Version in base suite: 1.30.4-1+deb13u3 Base version: mongo-c-driver_1.30.4-1+deb13u3 Target version: mongo-c-driver_1.30.4-1+deb13u5 Base file: /srv/ftp-master.debian.org/ftp/pool/main/m/mongo-c-driver/mongo-c-driver_1.30.4-1+deb13u3.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/m/mongo-c-driver/mongo-c-driver_1.30.4-1+deb13u5.dsc changelog | 47 patches/0009_CVE-2026-81524.patch | 12 patches/0010_CVE-2026-84965.patch | 303 + patches/0011_CVE-2026-84963.patch | 109 patches/0012_CVE-2026-84964.patch | 265 + patches/0013_CVE-2026-84969.patch | 102 patches/0014_CVE-2026-88035.patch | 172 patches/0015_CVE-2026-88036.patch | 9097 +++++++++++++++++++++++++++++++++++++ patches/0016_CVE-2026-93394.patch | 82 patches/0017_CVE-2026-96746.patch | 172 patches/0018_CVE-2026-106428.patch | 163 patches/0019_CVE-2026-106431.patch | 63 patches/0020_CVE-2026-106438.patch | 78 patches/0021_CVE-2026-106437.patch | 69 patches/0022_CVE-2026-120955.patch | 98 patches/0023_CVE-2026-120678.patch | 133 patches/0024_CVE-2026-120684.patch | 141 patches/0025_CDRIVER-6401.patch | 216 patches/0026_CVE-2026-120956.patch | 457 + patches/0027_CDRIVER-6523.patch | 2202 ++++++++ patches/series | 18 21 files changed, 13993 insertions(+), 6 deletions(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmp9fui9yjp/mongo-c-driver_1.30.4-1+deb13u3.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmp9fui9yjp/mongo-c-driver_1.30.4-1+deb13u5.dsc: no acceptable signature found diff -Nru mongo-c-driver-1.30.4/debian/changelog mongo-c-driver-1.30.4/debian/changelog --- mongo-c-driver-1.30.4/debian/changelog 2026-08-29 15:06:27.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/changelog 2026-11-03 21:12:33.000000000 +0000 @@ -1,3 +1,50 @@ +mongo-c-driver (1.30.4-1+deb13u5) trixie; urgency=medium + + * Fix CVE-2026-84965: heap write primitive via size round-up wrap during JSON + parsing on 32-bit builds + * Fix CVE-2026-84963: silent field truncation via unchecked int cast of huge + JSON string values in JSON-to-BSON parser + * Fix CVE-2026-84964: heap corruption via OCSP request double free from + crafted multi-URL certificate in TLS client + * Fix CVE-2026-84969: heap overflow via truncated base64 encoding of binary + fields in length-limited JSON output + * Fix CVE-2026-88035: heap buffer overflow via wrapped size check during SASL + username canonicalization + * Fix CVE-2026-88036: GridFS data disclosure and deletion via query-operator + injection in file IDs + * Fix CVE-2026-93394: SCRAM nonce validation bypass + * Fix CVE-2026-96746: heap buffer overflow via mid-scan command list growth + in client topology monitoring + * Fix CVE-2026-106428: out-of-bounds read in SCRAM authentication response + parsing + * Fix CVE-2026-106431: one-byte heap buffer overflow in BSON bulk document + writer + * Fix CVE-2026-106438: silent Decimal128 value corruption via incorrect + exactness check + * Fix CVE-2026-106437: out-of-bounds read and write via undersized BSON + buffer reservation + * Fix CVE-2026-120955: application abort on malformed GridFS metadata + * Fix CVE-2026-120678: leaks the decompressed reply buffer when an + OP_COMPRESSED reply fails to parse (denial of service) + * Fix CVE-2026-120684: GridFS gap lets a shared writer recover a protected + client's password + * Fix CDRIVER-6401: abort in cursor namespace parsing when no dot separator + (required by CVE-2026-120956 fix) + * Fix CVE-2026-120956: remote abort and memory disclosure from a malformed + cursor.ns + * Fix CDRIVER-6523: mandate raising writeConcernError (change SHOULD to MUST) + + this fixes a potential loss of data by ensuring that a WCE reported by + the server is propagated rather than discarded + + -- Roberto C. Sanchez Tue, 03 Nov 2026 16:12:33 -0500 + +mongo-c-driver (1.30.4-1+deb13u4) trixie; urgency=medium + + * Follow up to fix CVE-2026-81524: correct parameter ordering in function + definition. Thanks to Jeevan Chalke for the patch. (Closes: #1150302) + + -- Roberto C. Sanchez Fri, 09 Oct 2026 21:12:28 -0400 + mongo-c-driver (1.30.4-1+deb13u3) trixie; urgency=medium * Fix CVE-2026-81524: validate db and collection names diff -Nru mongo-c-driver-1.30.4/debian/patches/0009_CVE-2026-81524.patch mongo-c-driver-1.30.4/debian/patches/0009_CVE-2026-81524.patch --- mongo-c-driver-1.30.4/debian/patches/0009_CVE-2026-81524.patch 2026-08-29 15:06:27.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/patches/0009_CVE-2026-81524.patch 2026-11-03 21:12:33.000000000 +0000 @@ -32,22 +32,22 @@ const bson_t *opts, --- a/src/libmongoc/src/mongoc/mongoc-aggregate.c +++ b/src/libmongoc/src/mongoc/mongoc-aggregate.c -@@ -185,6 +185,7 @@ - * information on how to build aggregation pipelines. +@@ -186,6 +186,7 @@ * * Parameters: -+ * @db: Database name used. Separated from @ns to validate. * @ns: Namespace (or database name for database-level aggregation). ++ * @db: Database name used. Separated from @ns to validate. * @flags: Bitwise or of mongoc_query_flags_t or 0. * @pipeline: A bson_t containing the pipeline request. @pipeline -@@ -210,6 +211,7 @@ - + * will be sent as an array type in the request. +@@ -211,6 +212,7 @@ mongoc_cursor_t * _mongoc_aggregate (mongoc_client_t *client, -+ const char *db, const char *ns, ++ const char *db, mongoc_query_flags_t flags, const bson_t *pipeline, + const bson_t *opts, @@ -237,6 +239,7 @@ BSON_ASSERT (client); diff -Nru mongo-c-driver-1.30.4/debian/patches/0010_CVE-2026-84965.patch mongo-c-driver-1.30.4/debian/patches/0010_CVE-2026-84965.patch --- mongo-c-driver-1.30.4/debian/patches/0010_CVE-2026-84965.patch 1970-01-01 00:00:00.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/patches/0010_CVE-2026-84965.patch 2026-11-03 21:12:33.000000000 +0000 @@ -0,0 +1,303 @@ +From aba72444f8e8950b8a57636b1ad72a5a853f8264 Mon Sep 17 00:00:00 2001 +From: Kevin Albertson +Date: Fri, 31 Jul 2026 10:03:01 -0400 +Subject: [PATCH] CDRIVER-6405 fix unsafe calls to bson_next_power_of_two + +--- + src/common/src/common-bits-private.h | 28 ++++++++ + src/common/src/common-cmp-private.h | 18 +++++ + src/common/tests/test-common-bits.c | 55 +++++++++++++++++ + src/libbson/src/bson/bson-json.c | 19 ++++- + src/libmongoc/CMakeLists.txt | 1 + src/libmongoc/src/mongoc/mongoc-array.c | 13 ++-- + src/libmongoc/src/mongoc/mongoc-buffer.c | 9 ++ + src/libmongoc/src/mongoc/mongoc-secure-channel.c | 3 + src/libmongoc/src/mongoc/mongoc-topology-description.c | 3 + src/libmongoc/tests/test-libmongoc-main.c | 1 + 10 files changed, 136 insertions(+), 14 deletions(-) + create mode 100644 src/common/tests/test-common-bits.c + +--- a/src/common/src/common-bits-private.h ++++ b/src/common/src/common-bits-private.h +@@ -47,4 +47,32 @@ + } + + ++// Round up to the next power of two size_t value. Saturates on overflow. ++static BSON_INLINE size_t ++mcommon_next_power_of_two_size_t (size_t v) ++{ ++ if (v == 0) { ++ return 1; ++ } ++ ++ // https://graphics.stanford.edu/%7Eseander/bithacks.html#RoundUpPowerOf2 ++ v--; ++ v |= v >> 1; ++ v |= v >> 2; ++ v |= v >> 4; ++ v |= v >> 8; ++ v |= v >> 16; ++#if SIZE_MAX > 0xFFFFFFFFu ++ v |= v >> 32; ++#endif ++ v++; ++ ++ if (v == 0) { ++ return SIZE_MAX; ++ } else { ++ return v; ++ } ++} ++ ++ + #endif /* MONGO_C_DRIVER_COMMON_BITS_PRIVATE_H */ +--- a/src/common/src/common-cmp-private.h ++++ b/src/common/src/common-cmp-private.h +@@ -168,6 +168,24 @@ + #define mcommon_in_range_unsigned(Type, value) BSON_CONCAT3 (mcommon_in_range, _##Type, _unsigned) (value) + + ++/* Checked size_t arithmetic. Ports the `mlib_assert_add` / `mlib_assert_mul` ++ * helpers used upstream, which are unavailable on this branch. Both assert ++ * rather than wrap, matching upstream behavior. */ ++static BSON_INLINE size_t ++mcommon_assert_add_size_t (size_t a, size_t b) ++{ ++ BSON_ASSERT (a <= SIZE_MAX - b); ++ return a + b; ++} ++ ++static BSON_INLINE size_t ++mcommon_assert_mul_size_t (size_t a, size_t b) ++{ ++ BSON_ASSERT (a == 0u || b <= SIZE_MAX / a); ++ return a * b; ++} ++ ++ + BSON_END_DECLS + + +--- /dev/null ++++ b/src/common/tests/test-common-bits.c +@@ -0,0 +1,55 @@ ++/* ++ * Copyright 2009-present MongoDB, Inc. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++#include ++ ++#include ++ ++static void ++test_mcommon_next_power_of_two_u32 (void) ++{ ++ BSON_ASSERT (mcommon_next_power_of_two_u32 (0u) == 1u); ++ BSON_ASSERT (mcommon_next_power_of_two_u32 (1u) == 1u); ++ BSON_ASSERT (mcommon_next_power_of_two_u32 (3u) == 4u); ++ BSON_ASSERT (mcommon_next_power_of_two_u32 (UINT32_C (0x80000000)) == UINT32_C (0x80000000)); ++ ++ // Test saturation: ++ BSON_ASSERT (mcommon_next_power_of_two_u32 (UINT32_C (0x80000001)) == UINT32_MAX); ++ BSON_ASSERT (mcommon_next_power_of_two_u32 (UINT32_MAX) == UINT32_MAX); ++} ++ ++static void ++test_mcommon_next_power_of_two_size_t (void) ++{ ++ const size_t max_two_power = (SIZE_MAX >> 1u) + 1u; ++ ++ BSON_ASSERT (mcommon_next_power_of_two_size_t (0u) == 1u); ++ BSON_ASSERT (mcommon_next_power_of_two_size_t (1u) == 1u); ++ BSON_ASSERT (mcommon_next_power_of_two_size_t (3u) == 4u); ++ BSON_ASSERT (mcommon_next_power_of_two_size_t (max_two_power) == max_two_power); ++ ++ // Test saturation: ++ BSON_ASSERT (mcommon_next_power_of_two_size_t (max_two_power + 1u) == SIZE_MAX); ++ BSON_ASSERT (mcommon_next_power_of_two_size_t (SIZE_MAX - 1u) == SIZE_MAX); ++ BSON_ASSERT (mcommon_next_power_of_two_size_t (SIZE_MAX) == SIZE_MAX); ++} ++ ++void ++test_mcommon_bits_install (TestSuite *suite) ++{ ++ TestSuite_Add (suite, "/mcommon/bits/next_power_of_two/u32", test_mcommon_next_power_of_two_u32); ++ TestSuite_Add (suite, "/mcommon/bits/next_power_of_two/size_t", test_mcommon_next_power_of_two_size_t); ++} +--- a/src/libbson/src/bson/bson-json.c ++++ b/src/libbson/src/bson/bson-json.c +@@ -28,6 +28,7 @@ + + #include + #include ++#include + #include + + #ifdef _WIN32 +@@ -465,7 +466,7 @@ + if (buf->n_bytes < len) { + bson_free (buf->buf); + +- buf->n_bytes = bson_next_power_of_two (len); ++ buf->n_bytes = mcommon_next_power_of_two_size_t (len); + buf->buf = bson_malloc (buf->n_bytes); + } + } +@@ -474,7 +475,9 @@ + static void + _bson_json_buf_set (bson_json_buf_t *buf, const void *from, size_t len) + { +- _bson_json_buf_ensure (buf, len + 1); ++ const size_t len_with_null = mcommon_assert_add_size_t (len, 1u); ++ ++ _bson_json_buf_ensure (buf, len_with_null); + memcpy (buf->buf, from, len); + buf->buf[len] = '\0'; + buf->len = len; +@@ -484,13 +487,17 @@ + static void + _bson_json_buf_append (bson_json_buf_t *buf, const void *from, size_t len) + { +- size_t len_with_null = len + 1; ++ const size_t len_with_null = mcommon_assert_add_size_t (len, 1u); + + if (buf->len == 0) { + _bson_json_buf_ensure (buf, len_with_null); +- } else if (buf->n_bytes < buf->len + len_with_null) { +- buf->n_bytes = bson_next_power_of_two (buf->len + len_with_null); +- buf->buf = bson_realloc (buf->buf, buf->n_bytes); ++ } else { ++ const size_t needed = mcommon_assert_add_size_t (buf->len, len_with_null); ++ ++ if (buf->n_bytes < needed) { ++ buf->n_bytes = mcommon_next_power_of_two_size_t (needed); ++ buf->buf = bson_realloc (buf->buf, buf->n_bytes); ++ } + } + + memcpy (buf->buf + buf->len, from, len); +--- a/src/libmongoc/CMakeLists.txt ++++ b/src/libmongoc/CMakeLists.txt +@@ -1034,6 +1034,7 @@ + + set (test-libmongoc-sources + ${mongo-c-driver_SOURCE_DIR}/src/common/tests/test-common-atomic.c ++ ${mongo-c-driver_SOURCE_DIR}/src/common/tests/test-common-bits.c + ${mongo-c-driver_SOURCE_DIR}/src/common/tests/test-common-cmp.c + ${mongo-c-driver_SOURCE_DIR}/src/common/tests/test-common-oid.c + ${mongo-c-driver_SOURCE_DIR}/src/libbson/tests/corpus-test.c +--- a/src/libmongoc/src/mongoc/mongoc-array.c ++++ b/src/libmongoc/src/mongoc/mongoc-array.c +@@ -15,6 +15,8 @@ + */ + + ++#include ++#include + #include + + +@@ -100,10 +102,13 @@ + BSON_ASSERT (array); + BSON_ASSERT (data); + +- off = array->element_size * array->len; +- len = (size_t) n_elements * array->element_size; +- if ((off + len) > array->allocated) { +- next_size = bson_next_power_of_two (off + len); ++ off = mcommon_assert_mul_size_t (array->element_size, array->len); ++ len = mcommon_assert_mul_size_t ((size_t) n_elements, array->element_size); ++ ++ const size_t needed = mcommon_assert_add_size_t (off, len); ++ ++ if (needed > array->allocated) { ++ next_size = mcommon_next_power_of_two_size_t (needed); + + if (array->element_alignment == 0) { + array->data = bson_realloc (array->data, next_size); +--- a/src/libmongoc/src/mongoc/mongoc-buffer.c ++++ b/src/libmongoc/src/mongoc/mongoc-buffer.c +@@ -15,6 +15,9 @@ + */ + + ++#include ++#include ++ + #include + #include + +@@ -35,8 +38,10 @@ + static void + make_space_for (mongoc_buffer_t *buffer, size_t data_size) + { +- if (buffer->len + data_size > buffer->datalen) { +- buffer->datalen = bson_next_power_of_two (buffer->len + data_size); ++ const size_t needed = mcommon_assert_add_size_t (buffer->len, data_size); ++ ++ if (needed > buffer->datalen) { ++ buffer->datalen = mcommon_next_power_of_two_size_t (needed); + buffer->data = (uint8_t *) buffer->realloc_func (buffer->data, buffer->datalen, buffer->realloc_data); + } + } +--- a/src/libmongoc/src/mongoc/mongoc-secure-channel.c ++++ b/src/libmongoc/src/mongoc/mongoc-secure-channel.c +@@ -29,6 +29,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -435,7 +436,7 @@ + void + mongoc_secure_channel_realloc_buf (size_t *size, uint8_t **buf, size_t new_size) + { +- *size = bson_next_power_of_two (new_size); ++ *size = mcommon_next_power_of_two_size_t (new_size); + *buf = bson_realloc (*buf, *size); + } + +--- a/src/libmongoc/src/mongoc/mongoc-topology-description.c ++++ b/src/libmongoc/src/mongoc/mongoc-topology-description.c +@@ -14,6 +14,7 @@ + * limitations under the License. + */ + ++#include + #include + #include + #include +@@ -133,7 +134,7 @@ + dst->heartbeat_msec = src->heartbeat_msec; + dst->rand_seed = src->rand_seed; + +- nitems = bson_next_power_of_two (mc_tpld_servers_const (src)->items_len); ++ nitems = mcommon_next_power_of_two_size_t (mc_tpld_servers_const (src)->items_len); + dst->_servers_ = mongoc_set_new (nitems, _mongoc_topology_server_dtor, NULL); + for (size_t i = 0u; i < mc_tpld_servers_const (src)->items_len; i++) { + sd = mongoc_set_get_item_and_id_const (mc_tpld_servers_const (src), i, &id); +--- a/src/libmongoc/tests/test-libmongoc-main.c ++++ b/src/libmongoc/tests/test-libmongoc-main.c +@@ -47,6 +47,7 @@ + TEST_INSTALL (test_b64_install); + TEST_INSTALL (test_bson_cmp_install); + TEST_INSTALL (test_mcommon_cmp_install); ++ TEST_INSTALL (test_mcommon_bits_install); + TEST_INSTALL (test_mcommon_atomic_install); + TEST_INSTALL (test_mcommon_oid_install); + diff -Nru mongo-c-driver-1.30.4/debian/patches/0011_CVE-2026-84963.patch mongo-c-driver-1.30.4/debian/patches/0011_CVE-2026-84963.patch --- mongo-c-driver-1.30.4/debian/patches/0011_CVE-2026-84963.patch 1970-01-01 00:00:00.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/patches/0011_CVE-2026-84963.patch 2026-11-03 21:12:33.000000000 +0000 @@ -0,0 +1,109 @@ +From 16c91b9ab9ab4f917ef64c7a16947025a872434d Mon Sep 17 00:00:00 2001 +From: Kevin Albertson +Date: Wed, 2 Sep 2026 14:41:45 -0400 +Subject: [PATCH] CDRIVER-6407 check vlen before int cast in JSON parser + +--- + src/libbson/src/bson/bson-json.c | 15 +++++++++++++++ + src/libbson/tests/test-json.c | 33 ++++++++++++++++++++++++++++++++ + 2 files changed, 48 insertions(+) + +diff --git a/src/libbson/src/bson/bson-json.c b/src/libbson/src/bson/bson-json.c +index 3e765d137c..cd6809d9eb 100644 +--- a/src/libbson/src/bson/bson-json.c ++++ b/src/libbson/src/bson/bson-json.c +@@ -714,6 +714,11 @@ _bson_json_read_integer (bson_json_reader_t *reader, uint64_t val, int64_t sign) + static bool + _bson_json_parse_double (bson_json_reader_t *reader, const char *val, size_t vlen, double *d) + { ++ if (!mcommon_in_range_unsigned (int, vlen)) { ++ _bson_json_read_corrupt (reader, "string length out of range"); ++ return false; ++ } ++ + errno = 0; + *d = strtod (val, NULL); + +@@ -825,6 +830,11 @@ _bson_json_parse_binary_elem (bson_json_reader_t *reader, const char *val_w_null + + BASIC_CB_PREAMBLE; + ++ if (!mcommon_in_range_unsigned (int, vlen)) { ++ _bson_json_read_corrupt (reader, "string length out of range"); ++ return; ++ } ++ + bs = bson->bson_state; + data = &bson->bson_type_data; + +@@ -954,6 +964,11 @@ _bson_json_read_string (bson_json_reader_t *reader, /* IN */ + rs = bson->read_state; + bs = bson->bson_state; + ++ if (!mcommon_in_range_unsigned (int, vlen)) { ++ _bson_json_read_corrupt (reader, "string length out of range"); ++ return; ++ } ++ + if (!bson_utf8_validate ((const char *) val, vlen, allow_null)) { + _bson_json_read_corrupt (reader, "invalid bytes in UTF8 string"); + return; +diff --git a/src/libbson/tests/test-json.c b/src/libbson/tests/test-json.c +index 9695c5120c..108df04857 100644 +--- a/src/libbson/tests/test-json.c ++++ b/src/libbson/tests/test-json.c +@@ -6,6 +6,7 @@ + + #include "TestSuite.h" + #include "test-conveniences.h" ++#include "test-libmongoc.h" + #include + #include + #include +@@ -3792,6 +3793,37 @@ test_bson_as_json_all_formats (void) + } + + ++// Regression test for CDRIVER-6405: ++static void ++test_json_big_string (void *ctx) ++{ ++ BSON_UNUSED (ctx); ++ ++ // Create a JSON string like { "a": "b\0" + "c" * INT32_MAX ) } ++ static const char prefix[] = "{ \"a\": \"b\\u0000"; ++ static const char suffix[] = "\" }"; ++ const size_t prefix_len = sizeof prefix - 1u; ++ const size_t suffix_len = sizeof suffix - 1u; ++ const size_t filler_len = (size_t) INT32_MAX; ++ const size_t json_str_len = prefix_len + filler_len + suffix_len; ++ ++ char *json_str = bson_malloc (json_str_len + 1u); ++ memcpy (json_str, prefix, prefix_len); ++ memset (json_str + prefix_len, 'c', filler_len); ++ memcpy (json_str + prefix_len + filler_len, suffix, suffix_len); ++ json_str[json_str_len] = '\0'; ++ ++ // Try to parse with bson_init_from_json ++ bson_t b; ++ bson_error_t error = {0}; ++ BSON_ASSERT (mcommon_in_range_unsigned (ssize_t, json_str_len)); ++ const bool ok = bson_init_from_json (&b, json_str, (ssize_t) json_str_len, &error); ++ // Expect error: ++ ASSERT (!ok); ++ ASSERT_ERROR_CONTAINS (error, BSON_ERROR_JSON, BSON_JSON_ERROR_READ_CORRUPT_JS, "string length out of range"); ++ bson_free (json_str); ++} ++ + void + test_json_install (TestSuite *suite) + { +@@ -3897,4 +3929,5 @@ test_json_install (TestSuite *suite) + TestSuite_Add (suite, "/bson/parse_array", test_parse_array); + TestSuite_Add (suite, "/bson/decimal128_overflowing_exponent", test_decimal128_overflowing_exponent); + TestSuite_Add (suite, "/bson/as_json/all_formats", test_bson_as_json_all_formats); ++ TestSuite_AddFull (suite, "/bson/json/big_string", test_json_big_string, NULL, NULL, skip_if_no_large_allocations); + } +-- +2.39.5 + diff -Nru mongo-c-driver-1.30.4/debian/patches/0012_CVE-2026-84964.patch mongo-c-driver-1.30.4/debian/patches/0012_CVE-2026-84964.patch --- mongo-c-driver-1.30.4/debian/patches/0012_CVE-2026-84964.patch 1970-01-01 00:00:00.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/patches/0012_CVE-2026-84964.patch 2026-11-03 21:12:33.000000000 +0000 @@ -0,0 +1,265 @@ +From 7e4c66515a46abaf2f488ed0fe5a5c21887b6e1d Mon Sep 17 00:00:00 2001 +From: Connor MacDonald +Date: Wed, 12 Aug 2026 14:52:39 -0400 +Subject: [PATCH] CDRIVER-6409 fix OCSP double free + +--- + src/libmongoc/CMakeLists.txt | 1 + + .../src/mongoc/mongoc-openssl-private.h | 4 + + src/libmongoc/src/mongoc/mongoc-openssl.c | 36 +++---- + src/libmongoc/tests/test-libmongoc-main.c | 6 ++ + src/libmongoc/tests/test-mongoc-ocsp.c | 101 ++++++++++++++++++ + 5 files changed, 127 insertions(+), 21 deletions(-) + create mode 100644 src/libmongoc/tests/test-mongoc-ocsp.c + +diff --git a/src/libmongoc/CMakeLists.txt b/src/libmongoc/CMakeLists.txt +index d86d57beb6..31243c6e97 100644 +--- a/src/libmongoc/CMakeLists.txt ++++ b/src/libmongoc/CMakeLists.txt +@@ -1190,6 +1190,7 @@ if (MONGOC_ENABLE_SSL) + ${PROJECT_SOURCE_DIR}/tests/test-mongoc-stream-tls.c + ${PROJECT_SOURCE_DIR}/tests/test-mongoc-x509.c + ${PROJECT_SOURCE_DIR}/tests/test-mongoc-ocsp-cache.c ++ ${PROJECT_SOURCE_DIR}/tests/test-mongoc-ocsp.c + ) + endif () + +diff --git a/src/libmongoc/src/mongoc/mongoc-openssl-private.h b/src/libmongoc/src/mongoc/mongoc-openssl-private.h +index 95d2e4a858..60f6002fea 100644 +--- a/src/libmongoc/src/mongoc/mongoc-openssl-private.h ++++ b/src/libmongoc/src/mongoc/mongoc-openssl-private.h +@@ -28,6 +28,7 @@ + #include + + #if (OPENSSL_VERSION_NUMBER >= 0x10001000L) && !defined(OPENSSL_NO_OCSP) && !defined(LIBRESSL_VERSION_NUMBER) ++#include + #define MONGOC_ENABLE_OCSP_OPENSSL + #endif + +@@ -46,6 +47,9 @@ _mongoc_openssl_cleanup (void); + #ifdef MONGOC_ENABLE_OCSP_OPENSSL + int + _mongoc_ocsp_tlsext_status (SSL *ssl, mongoc_openssl_ocsp_opt_t *opts); ++ ++OCSP_RESPONSE * ++_mongoc_contact_ocsp_responder (OCSP_CERTID *id, X509 *peer, mongoc_ssl_opt_t *ssl_opts, int *ocsp_uri_count); + #endif + + bool +diff --git a/src/libmongoc/src/mongoc/mongoc-openssl.c b/src/libmongoc/src/mongoc/mongoc-openssl.c +index b5b7a56c11..a3f7fb0798 100644 +--- a/src/libmongoc/src/mongoc/mongoc-openssl.c ++++ b/src/libmongoc/src/mongoc/mongoc-openssl.c +@@ -624,15 +624,12 @@ _get_must_staple (X509 *cert) + #define ERR_STR (ERR_error_string (ERR_get_error (), NULL)) + #define MONGOC_OCSP_REQUEST_TIMEOUT_MS 5000 + +-static OCSP_RESPONSE * +-_contact_ocsp_responder (OCSP_CERTID *id, X509 *peer, mongoc_ssl_opt_t *ssl_opts, int *ocsp_uri_count) ++OCSP_RESPONSE * ++_mongoc_contact_ocsp_responder (OCSP_CERTID *id, X509 *peer, mongoc_ssl_opt_t *ssl_opts, int *ocsp_uri_count) + { + STACK_OF (OPENSSL_STRING) *url_stack = NULL; +- OPENSSL_STRING url = NULL, host = NULL, path = NULL, port = NULL; +- OCSP_REQUEST *req = NULL; +- const unsigned char *resp_data; + OCSP_RESPONSE *resp = NULL; +- int i, ssl; ++ int i; + + url_stack = X509_get1_ocsp (peer); + *ocsp_uri_count = sk_OPENSSL_STRING_num (url_stack); +@@ -645,9 +642,12 @@ _contact_ocsp_responder (OCSP_CERTID *id, X509 *peer, mongoc_ssl_opt_t *ssl_opts + + _mongoc_http_request_init (&http_req); + _mongoc_http_response_init (&http_res); +- url = sk_OPENSSL_STRING_value (url_stack, i); ++ const OPENSSL_STRING url = sk_OPENSSL_STRING_value (url_stack, i); + TRACE ("Contacting OCSP responder '%s'", url); + ++ OCSP_REQUEST *req = NULL; ++ OPENSSL_STRING host = NULL, path = NULL, port = NULL; ++ int ssl; + /* splits the given url into its host, port and path components */ + if (!OCSP_parse_url (url, &host, &port, &path, &ssl)) { + MONGOC_DEBUG ("Could not parse URL"); +@@ -689,7 +689,7 @@ _contact_ocsp_responder (OCSP_CERTID *id, X509 *peer, mongoc_ssl_opt_t *ssl_opts + GOTO (retry); + } + +- resp_data = (const unsigned char *) http_res.body; ++ const unsigned char *resp_data = (const unsigned char *) http_res.body; + + if (http_res.body_len == 0 || !d2i_OCSP_RESPONSE (&resp, &resp_data, http_res.body_len)) { + MONGOC_DEBUG ("Could not parse OCSP response from HTTP response"); +@@ -698,21 +698,15 @@ _contact_ocsp_responder (OCSP_CERTID *id, X509 *peer, mongoc_ssl_opt_t *ssl_opts + } + + retry: +- if (host) +- OPENSSL_free (host); +- if (port) +- OPENSSL_free (port); +- if (path) +- OPENSSL_free (path); +- if (req) +- OCSP_REQUEST_free (req); +- if (request_der) +- OPENSSL_free (request_der); ++ OPENSSL_free (host); ++ OPENSSL_free (port); ++ OPENSSL_free (path); ++ OCSP_REQUEST_free (req); ++ OPENSSL_free (request_der); + _mongoc_http_response_cleanup (&http_res); + } + +- if (url_stack) +- X509_email_free (url_stack); ++ X509_email_free (url_stack); + RETURN (resp); + } + +@@ -794,7 +788,7 @@ _mongoc_ocsp_tlsext_status (SSL *ssl, mongoc_openssl_ocsp_opt_t *opts) + } + + if (opts->disable_endpoint_check || +- !(resp = _contact_ocsp_responder (id, peer, &opts->ssl_opts, &ocsp_uri_count))) { ++ !(resp = _mongoc_contact_ocsp_responder (id, peer, &opts->ssl_opts, &ocsp_uri_count))) { + if (ocsp_uri_count > 0) { + /* Only log a soft failure if there were OCSP responders listed in + * the certificate. */ +diff --git a/src/libmongoc/tests/test-libmongoc-main.c b/src/libmongoc/tests/test-libmongoc-main.c +index 4f09ad8596..ede4fc753b 100644 +--- a/src/libmongoc/tests/test-libmongoc-main.c ++++ b/src/libmongoc/tests/test-libmongoc-main.c +@@ -3,6 +3,11 @@ + + #include + ++/* For MONGOC_ENABLE_OCSP_OPENSSL, which gates the OCSP test suites below. */ ++#ifdef MONGOC_ENABLE_SSL_OPENSSL ++#include ++#endif ++ + #include "test-libmongoc.h" + #include "TestSuite.h" + #include "test-conveniences.h" +@@ -141,6 +146,7 @@ main (int argc, char *argv[]) + TEST_INSTALL (test_streamable_hello_install); + #if defined(MONGOC_ENABLE_OCSP_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10101000L + TEST_INSTALL (test_ocsp_cache_install); ++ TEST_INSTALL (test_ocsp_install); + #endif + TEST_INSTALL (test_interrupt_install); + TEST_INSTALL (test_monitoring_install); +diff --git a/src/libmongoc/tests/test-mongoc-ocsp.c b/src/libmongoc/tests/test-mongoc-ocsp.c +new file mode 100644 +index 0000000000..9c631f91fe +--- /dev/null ++++ b/src/libmongoc/tests/test-mongoc-ocsp.c +@@ -0,0 +1,101 @@ ++/* ++ * Copyright 2009-present MongoDB, Inc. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++#include ++ ++#ifdef MONGOC_ENABLE_SSL_OPENSSL ++#include ++#endif ++ ++#include ++ ++#if defined(MONGOC_ENABLE_OCSP_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10101000L ++ ++#include ++ ++/* Appends an id-ad-ocsp accessLocation with the given URI to `aia`. The URI is ++ * not validated, so a deliberately malformed URI may be used. */ ++static void ++add_ocsp_uri (AUTHORITY_INFO_ACCESS *aia, const char *uri) ++{ ++ ACCESS_DESCRIPTION *const ad = ACCESS_DESCRIPTION_new (); ++ BSON_ASSERT (ad); ++ ++ ASN1_OBJECT_free (ad->method); ++ ad->method = OBJ_nid2obj (NID_ad_OCSP); ++ ++ ASN1_IA5STRING *const ia5 = ASN1_IA5STRING_new (); ++ BSON_ASSERT (ia5); ++ BSON_ASSERT (ASN1_STRING_set (ia5, uri, -1)); ++ GENERAL_NAME_set0_value (ad->location, GEN_URI, ia5); ++ ++ BSON_ASSERT (sk_ACCESS_DESCRIPTION_push (aia, ad)); ++} ++ ++/* Returns a certificate whose Authority Information Access extension lists ++ * `uris` as OCSP responder URIs, in order. */ ++static X509 * ++cert_with_ocsp_uris (const char *const *uris, size_t uris_len) ++{ ++ X509 *const cert = X509_new (); ++ BSON_ASSERT (cert); ++ ++ AUTHORITY_INFO_ACCESS *const aia = AUTHORITY_INFO_ACCESS_new (); ++ BSON_ASSERT (aia); ++ for (size_t i = 0; i < uris_len; i++) { ++ add_ocsp_uri (aia, uris[i]); ++ } ++ ++ BSON_ASSERT (X509_add1_ext_i2d (cert, NID_info_access, aia, 0 /* not critical */, 0 /* flags */)); ++ AUTHORITY_INFO_ACCESS_free (aia); ++ ++ return cert; ++} ++ ++/* Regression test for CDRIVER-6409 */ ++static void ++test_ocsp_responder_url_parse_errors (void) ++{ ++ const char *const uris[] = { ++ "http://localhost:1/", /* Parses; request is allocated, then freed. */ ++ "http://localhost:99999/", /* Port out of range: fails OCSP_parse_url ++ * before the request is reallocated. */ ++ }; ++ ++ X509 *const cert = cert_with_ocsp_uris (uris, sizeof (uris) / sizeof (uris[0])); ++ mongoc_ssl_opt_t ssl_opts = {0}; ++ int ocsp_uri_count = 0; ++ ++ /* A NULL cert ID makes the first URI fail after the OCSP_REQUEST is ++ * allocated, without any HTTP traffic. */ ++ OCSP_RESPONSE *const resp = _mongoc_contact_ocsp_responder (NULL /* id */, cert, &ssl_opts, &ocsp_uri_count); ++ ++ ASSERT_CMPINT (ocsp_uri_count, ==, 2); ++ BSON_ASSERT (!resp); ++ ++ X509_free (cert); ++} ++ ++void ++test_ocsp_install (TestSuite *suite) ++{ ++ TestSuite_Add (suite, "/OCSP/responder/url_parse_errors", test_ocsp_responder_url_parse_errors); ++} ++ ++#else ++/* ensure the translation unit is not empty */ ++extern int no_mongoc_ocsp_responder; ++#endif /* MONGOC_ENABLE_OCSP_OPENSSL */ +-- +2.39.5 + diff -Nru mongo-c-driver-1.30.4/debian/patches/0013_CVE-2026-84969.patch mongo-c-driver-1.30.4/debian/patches/0013_CVE-2026-84969.patch --- mongo-c-driver-1.30.4/debian/patches/0013_CVE-2026-84969.patch 1970-01-01 00:00:00.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/patches/0013_CVE-2026-84969.patch 2026-11-03 21:12:33.000000000 +0000 @@ -0,0 +1,102 @@ +From 08d0cfaaf08a54d7e87a5e5fa8d38251bf0eeca6 Mon Sep 17 00:00:00 2001 +From: Kevin Albertson +Date: Wed, 2 Sep 2026 14:41:44 -0400 +Subject: [PATCH] CDRIVER-6410 fix length used to copy truncated base64 + +--- + src/common/src/common-string.c | 2 +- + src/libbson/tests/test-string.c | 53 +++++++++++++++++++++++++++++++++ + 2 files changed, 54 insertions(+), 1 deletion(-) + +diff --git a/src/common/src/common-string.c b/src/common/src/common-string.c +index d01a76a6c4..60e4649f50 100644 +--- a/src/common/src/common-string.c ++++ b/src/common/src/common-string.c +@@ -239,7 +239,7 @@ mcommon_string_append_base64_encode (mcommon_string_append_t *append, const uint + (size_t) remainder_input_len, + remainder_buffer, + sizeof remainder_buffer)); +- memcpy (buffer + old_len + direct_encoded_len, remainder_buffer, remainder_encoded_len); ++ memcpy (buffer + old_len + direct_encoded_len, remainder_buffer, remainder_truncated_len); + + BSON_ASSERT (old_len + direct_encoded_len + remainder_truncated_len == max_len); + buffer[max_len] = '\0'; +diff --git a/src/libbson/tests/test-string.c b/src/libbson/tests/test-string.c +index f4ec563303..4de44ffef7 100644 +--- a/src/libbson/tests/test-string.c ++++ b/src/libbson/tests/test-string.c +@@ -16,6 +16,7 @@ + + + #include ++#include + #include + #include + +@@ -466,6 +467,57 @@ test_bson_string_capacity (void *unused) + bson_free (large_str); + } + ++// Test truncating a base64 append at various lengths. Regression test for CDRIVER-6410. ++static void ++test_string_append_base64_encode_truncated (void) ++{ ++ const uint8_t data[12] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'}; ++ char expected[17]; ++ // 12 input bytes encode to 16 base64 characters. ++ ASSERT_CMPINT (mcommon_b64_ntop (data, sizeof data, expected, sizeof expected), ==, 16); ++ ++ const uint8_t guard_byte = 0xa5; ++ const uint32_t guard_len = 8; ++ ++ // Truncate at every possible alignment within a 4 character base64 group. ++ for (uint32_t max_len = 4; max_len < 8; max_len++) { ++ // Over-allocate and fill the excess with a guard pattern to a write-past-the-end. ++ char *buffer = bson_malloc (max_len + 1u + guard_len); ++ buffer[0] = '\0'; ++ memset (buffer + max_len + 1u, guard_byte, guard_len); ++ ++ mcommon_string_t *string = mcommon_string_new_with_buffer (buffer, 0, max_len + 1u); ++ mcommon_string_append_t append; ++ mcommon_string_set_append_with_limit (string, &append, max_len); ++ ++ // Expect the append to return false indicating truncation. ++ ASSERT (!mcommon_string_append_base64_encode (&append, data, sizeof data)); ++ ++ // Expect exactly `max_len` characters of the encoding, NUL terminated. ++ ASSERT_CMPUINT32 (string->len, ==, max_len); ++ // Cast: r1.30's ASSERT_MEMCMP formats the length with %d. ++ ASSERT_MEMCMP (string->str, expected, (int) max_len); ++ ASSERT_CMPINT (string->str[max_len], ==, '\0'); ++ ++ // Expect nothing written past the NUL terminator. ++ uint32_t overwritten = 0; ++ for (uint32_t i = 0; i < guard_len; i++) { ++ if ((uint8_t) string->str[max_len + 1u + i] != guard_byte) { ++ overwritten++; ++ } ++ } ++ if (overwritten > 0) { ++ test_error ("wrote %" PRIu32 " byte(s) past the end of a %" PRIu32 " byte buffer (max_len=%" PRIu32 ")", ++ overwritten, ++ max_len + 1u, ++ max_len); ++ } ++ ++ mcommon_string_destroy (string); ++ } ++} ++ ++ + void + test_string_install (TestSuite *suite) + { +@@ -486,4 +538,5 @@ test_string_install (TestSuite *suite) + TestSuite_AddFull ( + suite, "/bson/string/capacity", test_bson_string_capacity, NULL, NULL, skip_if_no_large_allocations); + TestSuite_Add (suite, "/bson/string/truncate", test_bson_string_truncate); ++ TestSuite_Add (suite, "/bson/string/append_base64_encode/truncated", test_string_append_base64_encode_truncated); + } +-- +2.39.5 + diff -Nru mongo-c-driver-1.30.4/debian/patches/0014_CVE-2026-88035.patch mongo-c-driver-1.30.4/debian/patches/0014_CVE-2026-88035.patch --- mongo-c-driver-1.30.4/debian/patches/0014_CVE-2026-88035.patch 1970-01-01 00:00:00.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/patches/0014_CVE-2026-88035.patch 2026-11-03 21:12:33.000000000 +0000 @@ -0,0 +1,172 @@ +From ddfe9e5fe9e3e43ce8f3b1429cacc872767ee2ba Mon Sep 17 00:00:00 2001 +From: Connor MacDonald +Date: Mon, 17 Aug 2026 09:30:46 -0400 +Subject: [PATCH] CDRIVER-6416 check size during SASL username canonicalization + +--- + src/libmongoc/src/mongoc/mongoc-cyrus-private.h | 11 +++ + src/libmongoc/src/mongoc/mongoc-cyrus.c | 16 ++--- + src/libmongoc/tests/test-mongoc-cyrus.c | 73 ++++++++++++++++++++++++ + 3 files changed, 93 insertions(+), 7 deletions(-) + +--- a/src/libmongoc/src/mongoc/mongoc-cyrus-private.h ++++ b/src/libmongoc/src/mongoc/mongoc-cyrus-private.h +@@ -48,6 +48,17 @@ + + int + _mongoc_cyrus_verifyfile_cb (void *context, const char *file, sasl_verify_type_t type); ++// `_mongoc_cyrus_canon_user` is the SASL_CB_CANON_USER callback. Exported for testing. ++int ++_mongoc_cyrus_canon_user(sasl_conn_t *conn, ++ mongoc_cyrus_t *sasl, ++ const char *in, ++ unsigned inlen, ++ unsigned flags, ++ const char *user_realm, ++ char *out, ++ unsigned out_max, ++ unsigned *out_len); + void + _mongoc_cyrus_init (mongoc_cyrus_t *sasl); + bool +--- a/src/libmongoc/src/mongoc/mongoc-cyrus.c ++++ b/src/libmongoc/src/mongoc/mongoc-cyrus.c +@@ -18,6 +18,7 @@ + + #ifdef MONGOC_ENABLE_SASL_CYRUS + ++#include + #include + + #include +@@ -89,7 +90,7 @@ + } + + +-static int ++int + _mongoc_cyrus_canon_user (sasl_conn_t *conn, + mongoc_cyrus_t *sasl, + const char *in, +@@ -105,18 +106,19 @@ + BSON_UNUSED (flags); + BSON_UNUSED (user_realm); + +- // `inlen` is a string length (excluding trailing NULL). +- // Cyrus-SASL passes an `out` buffer of size `out_max + 1`. Assume `out_max` is the max to be safe. +- const unsigned inlen_2 = inlen + 2u; +- if (inlen_2 < inlen || inlen_2 >= out_max) { ++ if (inlen > out_max) { + MONGOC_ERROR ("SASL username too large"); + return SASL_BUFOVER; + } + +- TRACE ("Canonicalizing %s (%" PRIu32 ")\n", in, inlen); ++ // Print with a precision: `in` is length-delimited, and Cyrus-SASL documents it as "may not be NUL terminated", so ++ // `%s` would read past the end. The precision argument is an `int`, and a negative one "is taken as if the precision ++ // were omitted" (C99 7.19.6.1) — quietly restoring that `%s` overread — so clamp rather than cast blindly. The check ++ // above already bounds `inlen` by `out_max`, so this only matters if a caller ever passes an `out_max` above ++ // `INT_MAX`. ++ TRACE ("Canonicalizing %.*s (%u)\n", (int) BSON_MIN (inlen, (unsigned) INT_MAX), in, inlen); + // Use memmove in case buffers overlap. From Cyrus-SASL: "output buffers and the input buffers may be the same" + memmove (out, in, inlen); +- out[inlen] = '\0'; + *out_len = inlen; + return SASL_OK; + } +--- a/src/libmongoc/tests/test-mongoc-cyrus.c ++++ b/src/libmongoc/tests/test-mongoc-cyrus.c +@@ -22,6 +22,8 @@ + #include "test-libmongoc.h" + #include "test-conveniences.h" + ++#include ++ + + static void + test_sasl_properties (void) +@@ -63,6 +65,74 @@ + } + + ++#define CANON_OUT_MAX 255u ++ ++static void ++test_sasl_canon_user(void) ++{ ++ char *const out = bson_malloc(CANON_OUT_MAX); ++ unsigned out_len = 0u; ++ const char in[] = {'u', 's', 'e', 'r'}; ++ ++ ASSERT_CMPINT( ++ _mongoc_cyrus_canon_user(NULL, NULL, in, sizeof in, 0u, NULL, out, CANON_OUT_MAX, &out_len), ==, SASL_OK); ++ ++ // The output is deliberately not NUL terminated; Cyrus-SASL reads exactly `out_len` bytes. ++ ASSERT_CMPUINT(out_len, ==, (unsigned)sizeof in); ++ ASSERT(0 == memcmp(out, in, sizeof in)); ++ ++ bson_free(out); ++} ++ ++// A username of exactly `out_max` bytes is a full-length username, not an overflow. ++static void ++test_sasl_canon_user_longest_accepted(void) ++{ ++ const unsigned inlen = CANON_OUT_MAX; ++ char *const out = bson_malloc(inlen); ++ char *const in = bson_malloc(inlen); ++ unsigned out_len = 0u; ++ ++ memset(in, 'x', inlen); ++ ++ ASSERT_CMPINT(_mongoc_cyrus_canon_user(NULL, NULL, in, inlen, 0u, NULL, out, CANON_OUT_MAX, &out_len), ==, SASL_OK); ++ ++ ASSERT_CMPUINT(out_len, ==, inlen); ++ ASSERT(0 == memcmp(out, in, inlen)); ++ ++ bson_free(in); ++ bson_free(out); ++} ++ ++// Regression test for CDRIVER-6416. ++static void ++test_sasl_canon_user_too_large(void) ++{ ++ const unsigned oversized[] = {CANON_OUT_MAX + 1u, UINT_MAX - 1u, UINT_MAX}; ++ ++ capture_logs(true); ++ ++ for (size_t i = 0u; i < sizeof oversized / sizeof *oversized; i++) { ++ char *const out = bson_malloc(CANON_OUT_MAX); ++ unsigned out_len = 0u; ++ const char in[] = {'x'}; ++ ++ ASSERT_WITH_MSG(_mongoc_cyrus_canon_user(NULL, NULL, in, oversized[i], 0u, NULL, out, CANON_OUT_MAX, &out_len) == ++ SASL_BUFOVER, ++ "expected SASL_BUFOVER for inlen %u", ++ oversized[i]); ++ ++ ASSERT_CAPTURED_LOG("oversized SASL username", MONGOC_LOG_LEVEL_ERROR, "SASL username too large"); ++ clear_captured_logs(); ++ ++ bson_free(out); ++ } ++ ++ capture_logs(false); ++} ++ ++#undef CANON_OUT_MAX ++ + static void + test_sasl_canonicalize_hostname (void *ctx) + { +@@ -90,6 +160,9 @@ + test_cyrus_install (TestSuite *suite) + { + TestSuite_Add (suite, "/SASL/properties", test_sasl_properties); ++ TestSuite_Add (suite, "/SASL/canon_user", test_sasl_canon_user); ++ TestSuite_Add (suite, "/SASL/canon_user/longest_accepted", test_sasl_canon_user_longest_accepted); ++ TestSuite_Add (suite, "/SASL/canon_user/too_large", test_sasl_canon_user_too_large); + TestSuite_AddFull (suite, + "/SASL/canonicalize", + test_sasl_canonicalize_hostname, diff -Nru mongo-c-driver-1.30.4/debian/patches/0015_CVE-2026-88036.patch mongo-c-driver-1.30.4/debian/patches/0015_CVE-2026-88036.patch --- mongo-c-driver-1.30.4/debian/patches/0015_CVE-2026-88036.patch 1970-01-01 00:00:00.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/patches/0015_CVE-2026-88036.patch 2026-11-03 21:12:33.000000000 +0000 @@ -0,0 +1,9097 @@ +From 6197ab3d7a24f3323a2cf297aae81c7cec96264b Mon Sep 17 00:00:00 2001 +From: Kevin Albertson +Date: Wed, 9 Sep 2026 08:35:29 -0400 +Subject: [PATCH] CDRIVER-6427 use exact match for file ID in GridFS delete + methods + +--- + .../src/mongoc/mongoc-gridfs-bucket-file.c | 4 +- + .../src/mongoc/mongoc-gridfs-bucket.c | 17 +- + src/libmongoc/src/mongoc/mongoc-gridfs-file.c | 12 +- + src/libmongoc/tests/json-test.c | 4 +- + .../json/gridfs/{ => legacy}/delete.json | 0 + .../json/gridfs/{ => legacy}/download.json | 0 + .../gridfs/{ => legacy}/download_by_name.json | 0 + .../json/gridfs/{ => legacy}/upload.json | 0 + src/libmongoc/src/mongoc/mongoc-gridfs-bucket-file.c | 4 + src/libmongoc/src/mongoc/mongoc-gridfs-bucket.c | 17 + src/libmongoc/src/mongoc/mongoc-gridfs-file.c | 12 + src/libmongoc/tests/json-test.c | 4 + src/libmongoc/tests/json/gridfs/delete.json | 291 -- + src/libmongoc/tests/json/gridfs/download.json | 467 ---- + src/libmongoc/tests/json/gridfs/download_by_name.json | 240 -- + src/libmongoc/tests/json/gridfs/legacy/delete.json | 291 ++ + src/libmongoc/tests/json/gridfs/legacy/download.json | 467 ++++ + src/libmongoc/tests/json/gridfs/legacy/download_by_name.json | 240 ++ + src/libmongoc/tests/json/gridfs/legacy/upload.json | 466 ++++ + src/libmongoc/tests/json/gridfs/unified/queries-use-eq.json | 528 ++++ + src/libmongoc/tests/json/gridfs/upload.json | 466 ---- + src/libmongoc/tests/json/retryable_reads/legacy/gridfs-download-serverErrors.json | 925 -------- + src/libmongoc/tests/json/retryable_reads/legacy/gridfs-download.json | 270 -- + src/libmongoc/tests/json/retryable_reads/legacy/gridfs-downloadByName-serverErrors.json | 849 ------- + src/libmongoc/tests/json/retryable_reads/legacy/gridfs-downloadByName.json | 250 -- + src/libmongoc/tests/json/retryable_reads/unified/gridfs-download-serverErrors.json | 1142 ++++++++++ + src/libmongoc/tests/json/retryable_reads/unified/gridfs-download.json | 379 +++ + src/libmongoc/tests/json/retryable_reads/unified/gridfs-downloadByName-serverErrors.json | 1016 ++++++++ + src/libmongoc/tests/json/retryable_reads/unified/gridfs-downloadByName.json | 347 +++ + src/libmongoc/tests/test-mongoc-gridfs-bucket.c | 77 + src/libmongoc/tests/test-mongoc-gridfs.c | 59 + src/libmongoc/tests/unified/runner.c | 11 + 24 files changed, 5034 insertions(+), 3784 deletions(-) + rename src/libmongoc/tests/json/gridfs/{ => legacy}/delete.json (100%) + rename src/libmongoc/tests/json/gridfs/{ => legacy}/download.json (100%) + rename src/libmongoc/tests/json/gridfs/{ => legacy}/download_by_name.json (100%) + rename src/libmongoc/tests/json/gridfs/{ => legacy}/upload.json (100%) + create mode 100644 src/libmongoc/tests/json/gridfs/unified/queries-use-eq.json + delete mode 100644 src/libmongoc/tests/json/retryable_reads/legacy/gridfs-download-serverErrors.json + delete mode 100644 src/libmongoc/tests/json/retryable_reads/legacy/gridfs-download.json + delete mode 100644 src/libmongoc/tests/json/retryable_reads/legacy/gridfs-downloadByName-serverErrors.json + delete mode 100644 src/libmongoc/tests/json/retryable_reads/legacy/gridfs-downloadByName.json + create mode 100644 src/libmongoc/tests/json/retryable_reads/unified/gridfs-download-serverErrors.json + create mode 100644 src/libmongoc/tests/json/retryable_reads/unified/gridfs-download.json + create mode 100644 src/libmongoc/tests/json/retryable_reads/unified/gridfs-downloadByName-serverErrors.json + create mode 100644 src/libmongoc/tests/json/retryable_reads/unified/gridfs-downloadByName.json + +--- a/src/libmongoc/src/mongoc/mongoc-gridfs-bucket-file.c ++++ b/src/libmongoc/src/mongoc/mongoc-gridfs-bucket-file.c +@@ -15,6 +15,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -168,11 +169,10 @@ + + BSON_ASSERT (file); + +- bson_init (&filter); + bson_init (&opts); + bson_init (&sort); + +- BSON_APPEND_VALUE (&filter, "files_id", file->file_id); ++ bsonBuild (filter, kv ("files_id", doc (kv ("$eq", value (*file->file_id))))); + BSON_APPEND_INT32 (&sort, "n", 1); + BSON_APPEND_DOCUMENT (&opts, "sort", &sort); + +--- a/src/libmongoc/src/mongoc/mongoc-gridfs-bucket.c ++++ b/src/libmongoc/src/mongoc/mongoc-gridfs-bucket.c +@@ -16,6 +16,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -55,9 +56,7 @@ + BSON_ASSERT (bucket); + BSON_ASSERT (file_id); + +- bson_init (&filter); +- +- BSON_APPEND_VALUE (&filter, "_id", file_id); ++ bsonBuild (filter, kv ("_id", doc (kv ("$eq", value (*file_id))))); + + cursor = mongoc_collection_find_with_opts (bucket->files, &filter, NULL, NULL); + bson_destroy (&filter); +@@ -435,9 +434,7 @@ + BSON_ASSERT (bucket); + BSON_ASSERT (file_id); + +- bson_init (&files_selector); +- +- BSON_APPEND_VALUE (&files_selector, "_id", file_id); ++ bsonBuild (files_selector, kv ("_id", doc (kv ("$eq", value (*file_id))))); + + r = mongoc_collection_delete_one (bucket->files, &files_selector, NULL, &reply, error); + bson_destroy (&files_selector); +@@ -456,9 +453,7 @@ + + bson_destroy (&reply); + +- bson_init (&chunks_selector); +- +- BSON_APPEND_VALUE (&chunks_selector, "files_id", file_id); ++ bsonBuild (chunks_selector, kv ("files_id", doc (kv ("$eq", value (*file_id))))); + + r = mongoc_collection_delete_many (bucket->chunks, &chunks_selector, NULL, NULL, error); + bson_destroy (&chunks_selector); +@@ -535,9 +530,7 @@ + * collection when the stream is closed */ + file->saved = true; + +- bson_init (&chunks_selector); +- BSON_APPEND_VALUE (&chunks_selector, "files_id", file->file_id); +- ++ bsonBuild (chunks_selector, kv ("files_id", doc (kv ("$eq", value (*file->file_id))))); + r = mongoc_collection_delete_many (file->bucket->chunks, &chunks_selector, NULL, NULL, &file->err); + bson_destroy (&chunks_selector); + return r; +--- a/src/libmongoc/src/mongoc/mongoc-gridfs-file.c ++++ b/src/libmongoc/src/mongoc/mongoc-gridfs-file.c +@@ -23,6 +23,7 @@ + #include + + #include ++#include + #include + #include + #include +@@ -146,7 +147,7 @@ + metadata = mongoc_gridfs_file_get_metadata (file); + + selector = bson_new (); +- bson_append_value (selector, "_id", -1, &file->files_id); ++ bsonBuildAppend (*selector, kv ("_id", doc (kv ("$eq", value (file->files_id))))); + + update = bson_new (); + bson_append_document_begin (update, "$set", -1, &child); +@@ -642,7 +643,7 @@ + + selector = bson_new (); + +- bson_append_value (selector, "files_id", -1, &file->files_id); ++ bsonBuildAppend (*selector, kv ("files_id", doc (kv ("$eq", value (file->files_id))))); + bson_append_int32 (selector, "n", -1, file->n); + + update = bson_sized_new (file->chunk_size + 100); +@@ -780,8 +781,7 @@ + + if (!file->cursor) { + bson_t query; +- bson_init (&query); +- BSON_APPEND_VALUE (&query, "files_id", &file->files_id); ++ bsonBuild (query, kv ("files_id", doc (kv ("$eq", value (file->files_id))))); + + bson_t child; + BSON_APPEND_DOCUMENT_BEGIN (&query, "n", &child); +@@ -1045,14 +1045,14 @@ + + BSON_ASSERT (file); + +- BSON_APPEND_VALUE (&sel, "_id", &file->files_id); ++ bsonBuildAppend (sel, kv ("_id", doc (kv ("$eq", value (file->files_id))))); + + if (!mongoc_collection_delete_one (file->gridfs->files, &sel, NULL, NULL, error)) { + goto cleanup; + } + + bson_reinit (&sel); +- BSON_APPEND_VALUE (&sel, "files_id", &file->files_id); ++ bsonBuildAppend (sel, kv ("files_id", doc (kv ("$eq", value (file->files_id))))); + + if (!mongoc_collection_delete_many (file->gridfs->chunks, &sel, NULL, NULL, error)) { + goto cleanup; +--- a/src/libmongoc/tests/json-test.c ++++ b/src/libmongoc/tests/json-test.c +@@ -1821,9 +1821,7 @@ + { + int i; + bool skip = false; +- const char *unsupported_tests[] = {"/retryable_reads/legacy/gridfs-downloadByName", +- "/retryable_reads/legacy/gridfs-downloadByName-serverErrors", +- "/retryable_reads/legacy/listCollectionObjects", ++ const char *unsupported_tests[] = {"/retryable_reads/legacy/listCollectionObjects", + "/retryable_reads/legacy/listCollectionObjects-serverErrors", + "/retryable_reads/legacy/listDatabaseObjects", + "/retryable_reads/legacy/listDatabaseObjects-serverErrors", +--- /dev/null ++++ b/src/libmongoc/tests/json/gridfs/unified/queries-use-eq.json +@@ -0,0 +1,528 @@ ++{ ++ "description": "gridfs-queriesUseEq", ++ "schemaVersion": "1.1", ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "observeEvents": [ ++ "commandStartedEvent" ++ ] ++ } ++ }, ++ { ++ "database": { ++ "id": "database0", ++ "client": "client0", ++ "databaseName": "gridfs-tests" ++ } ++ }, ++ { ++ "bucket": { ++ "id": "bucket0", ++ "database": "database0" ++ } ++ }, ++ { ++ "collection": { ++ "id": "bucket0_files_collection", ++ "database": "database0", ++ "collectionName": "fs.files" ++ } ++ }, ++ { ++ "collection": { ++ "id": "bucket0_chunks_collection", ++ "database": "database0", ++ "collectionName": "fs.chunks" ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "collectionName": "fs.files", ++ "databaseName": "gridfs-tests", ++ "documents": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "length": 2, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "filename": "file-1" ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000002" ++ }, ++ "length": 0, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "filename": "file-2" ++ } ++ ] ++ }, ++ { ++ "collectionName": "fs.chunks", ++ "databaseName": "gridfs-tests", ++ "documents": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000011" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "n": 0, ++ "data": { ++ "$binary": { ++ "base64": "ESI=", ++ "subType": "00" ++ } ++ } ++ } ++ ] ++ } ++ ], ++ "_yamlAnchors": { ++ "injectedId": { ++ "$gt": { ++ "$minKey": 1 ++ } ++ }, ++ "unchangedOutcome": [ ++ { ++ "collectionName": "fs.files", ++ "databaseName": "gridfs-tests", ++ "documents": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "length": 2, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "filename": "file-1" ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000002" ++ }, ++ "length": 0, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "filename": "file-2" ++ } ++ ] ++ }, ++ { ++ "collectionName": "fs.chunks", ++ "databaseName": "gridfs-tests", ++ "documents": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000011" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "n": 0, ++ "data": { ++ "$binary": { ++ "base64": "ESI=", ++ "subType": "00" ++ } ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ "tests": [ ++ { ++ "description": "download uses $eq to query the files and chunks collections", ++ "operations": [ ++ { ++ "name": "download", ++ "object": "bucket0", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "1122" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ } ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks", ++ "filter": { ++ "files_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ } ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "delete uses $eq to delete the files collection document and chunks", ++ "operations": [ ++ { ++ "name": "delete", ++ "object": "bucket0", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "delete": "fs.files", ++ "deletes": [ ++ { ++ "q": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ }, ++ "limit": 1 ++ } ++ ] ++ } ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "delete": "fs.chunks", ++ "deletes": [ ++ { ++ "q": { ++ "files_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ }, ++ "limit": 0 ++ } ++ ] ++ } ++ } ++ } ++ ] ++ } ++ ], ++ "outcome": [ ++ { ++ "collectionName": "fs.files", ++ "databaseName": "gridfs-tests", ++ "documents": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000002" ++ }, ++ "length": 0, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "filename": "file-2" ++ } ++ ] ++ }, ++ { ++ "collectionName": "fs.chunks", ++ "databaseName": "gridfs-tests", ++ "documents": [] ++ } ++ ] ++ }, ++ { ++ "description": "rename uses $eq to update the files collection document", ++ "operations": [ ++ { ++ "name": "rename", ++ "object": "bucket0", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "newFilename": "file-1-renamed" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "update": "fs.files", ++ "updates": [ ++ { ++ "q": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ }, ++ "u": { ++ "$set": { ++ "filename": "file-1-renamed" ++ } ++ }, ++ "multi": { ++ "$$unsetOrMatches": false ++ }, ++ "upsert": { ++ "$$unsetOrMatches": false ++ } ++ } ++ ] ++ } ++ } ++ } ++ ] ++ } ++ ], ++ "outcome": [ ++ { ++ "collectionName": "fs.files", ++ "databaseName": "gridfs-tests", ++ "documents": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "length": 2, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "filename": "file-1-renamed" ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000002" ++ }, ++ "length": 0, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "filename": "file-2" ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "delete with a file id containing a query operator does not delete any files", ++ "operations": [ ++ { ++ "name": "delete", ++ "object": "bucket0", ++ "arguments": { ++ "id": { ++ "$gt": { ++ "$minKey": 1 ++ } ++ } ++ }, ++ "expectError": { ++ "isError": true ++ } ++ } ++ ], ++ "outcome": [ ++ { ++ "collectionName": "fs.files", ++ "databaseName": "gridfs-tests", ++ "documents": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "length": 2, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "filename": "file-1" ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000002" ++ }, ++ "length": 0, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "filename": "file-2" ++ } ++ ] ++ }, ++ { ++ "collectionName": "fs.chunks", ++ "databaseName": "gridfs-tests", ++ "documents": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000011" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "n": 0, ++ "data": { ++ "$binary": { ++ "base64": "ESI=", ++ "subType": "00" ++ } ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "download with a file id containing a query operator does not return any files", ++ "operations": [ ++ { ++ "name": "download", ++ "object": "bucket0", ++ "arguments": { ++ "id": { ++ "$gt": { ++ "$minKey": 1 ++ } ++ } ++ }, ++ "expectError": { ++ "isClientError": true ++ } ++ } ++ ] ++ }, ++ { ++ "description": "rename with a file id containing a query operator does not rename any files", ++ "operations": [ ++ { ++ "name": "rename", ++ "object": "bucket0", ++ "arguments": { ++ "id": { ++ "$gt": { ++ "$minKey": 1 ++ } ++ }, ++ "newFilename": "injected" ++ }, ++ "expectError": { ++ "isError": true ++ } ++ } ++ ], ++ "outcome": [ ++ { ++ "collectionName": "fs.files", ++ "databaseName": "gridfs-tests", ++ "documents": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "length": 2, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "filename": "file-1" ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000002" ++ }, ++ "length": 0, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "filename": "file-2" ++ } ++ ] ++ }, ++ { ++ "collectionName": "fs.chunks", ++ "databaseName": "gridfs-tests", ++ "documents": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000011" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "n": 0, ++ "data": { ++ "$binary": { ++ "base64": "ESI=", ++ "subType": "00" ++ } ++ } ++ } ++ ] ++ } ++ ] ++ } ++ ] ++} +--- a/src/libmongoc/tests/json/retryable_reads/legacy/gridfs-download-serverErrors.json ++++ /dev/null +@@ -1,925 +0,0 @@ +-{ +- "runOn": [ +- { +- "minServerVersion": "4.0", +- "topology": [ +- "single", +- "replicaset" +- ] +- }, +- { +- "minServerVersion": "4.1.7", +- "topology": [ +- "sharded", +- "load-balanced" +- ] +- } +- ], +- "database_name": "retryable-reads-tests", +- "bucket_name": "fs", +- "data": { +- "fs.files": [ +- { +- "_id": { +- "$oid": "000000000000000000000001" +- }, +- "length": 1, +- "chunkSize": 4, +- "uploadDate": { +- "$date": "1970-01-01T00:00:00.000Z" +- }, +- "filename": "abc", +- "metadata": {} +- } +- ], +- "fs.chunks": [ +- { +- "_id": { +- "$oid": "000000000000000000000002" +- }, +- "files_id": { +- "$oid": "000000000000000000000001" +- }, +- "n": 0, +- "data": { +- "$binary": { +- "base64": "EQ==", +- "subType": "00" +- } +- } +- } +- ] +- }, +- "tests": [ +- { +- "description": "Download succeeds after InterruptedAtShutdown", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 11600 +- } +- }, +- "operations": [ +- { +- "name": "download", +- "object": "gridfsbucket", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000001" +- } +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "Download succeeds after InterruptedDueToReplStateChange", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 11602 +- } +- }, +- "operations": [ +- { +- "name": "download", +- "object": "gridfsbucket", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000001" +- } +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "Download succeeds after NotWritablePrimary", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 10107 +- } +- }, +- "operations": [ +- { +- "name": "download", +- "object": "gridfsbucket", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000001" +- } +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "Download succeeds after NotPrimaryNoSecondaryOk", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 13435 +- } +- }, +- "operations": [ +- { +- "name": "download", +- "object": "gridfsbucket", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000001" +- } +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "Download succeeds after NotPrimaryOrSecondary", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 13436 +- } +- }, +- "operations": [ +- { +- "name": "download", +- "object": "gridfsbucket", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000001" +- } +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "Download succeeds after PrimarySteppedDown", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 189 +- } +- }, +- "operations": [ +- { +- "name": "download", +- "object": "gridfsbucket", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000001" +- } +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "Download succeeds after ShutdownInProgress", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 91 +- } +- }, +- "operations": [ +- { +- "name": "download", +- "object": "gridfsbucket", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000001" +- } +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "Download succeeds after HostNotFound", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 7 +- } +- }, +- "operations": [ +- { +- "name": "download", +- "object": "gridfsbucket", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000001" +- } +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "Download succeeds after HostUnreachable", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 6 +- } +- }, +- "operations": [ +- { +- "name": "download", +- "object": "gridfsbucket", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000001" +- } +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "Download succeeds after NetworkTimeout", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 89 +- } +- }, +- "operations": [ +- { +- "name": "download", +- "object": "gridfsbucket", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000001" +- } +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "Download succeeds after SocketException", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 9001 +- } +- }, +- "operations": [ +- { +- "name": "download", +- "object": "gridfsbucket", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000001" +- } +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "Download fails after two NotWritablePrimary errors", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 2 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 10107 +- } +- }, +- "operations": [ +- { +- "name": "download", +- "object": "gridfsbucket", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "error": true +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "Download fails after NotWritablePrimary when retryReads is false", +- "clientOptions": { +- "retryReads": false +- }, +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 10107 +- } +- }, +- "operations": [ +- { +- "name": "download", +- "object": "gridfsbucket", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "error": true +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- } +- ] +-} +--- a/src/libmongoc/tests/json/retryable_reads/legacy/gridfs-download.json ++++ /dev/null +@@ -1,270 +0,0 @@ +-{ +- "runOn": [ +- { +- "minServerVersion": "4.0", +- "topology": [ +- "single", +- "replicaset" +- ] +- }, +- { +- "minServerVersion": "4.1.7", +- "topology": [ +- "sharded", +- "load-balanced" +- ] +- } +- ], +- "database_name": "retryable-reads-tests", +- "bucket_name": "fs", +- "data": { +- "fs.files": [ +- { +- "_id": { +- "$oid": "000000000000000000000001" +- }, +- "length": 1, +- "chunkSize": 4, +- "uploadDate": { +- "$date": "1970-01-01T00:00:00.000Z" +- }, +- "filename": "abc", +- "metadata": {} +- } +- ], +- "fs.chunks": [ +- { +- "_id": { +- "$oid": "000000000000000000000002" +- }, +- "files_id": { +- "$oid": "000000000000000000000001" +- }, +- "n": 0, +- "data": { +- "$binary": { +- "base64": "EQ==", +- "subType": "00" +- } +- } +- } +- ] +- }, +- "tests": [ +- { +- "description": "Download succeeds on first attempt", +- "operations": [ +- { +- "name": "download", +- "object": "gridfsbucket", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000001" +- } +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "Download succeeds on second attempt", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "closeConnection": true +- } +- }, +- "operations": [ +- { +- "name": "download", +- "object": "gridfsbucket", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000001" +- } +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "Download fails on first attempt", +- "clientOptions": { +- "retryReads": false +- }, +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "closeConnection": true +- } +- }, +- "operations": [ +- { +- "name": "download", +- "object": "gridfsbucket", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "error": true +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "Download fails on second attempt", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 2 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "closeConnection": true +- } +- }, +- "operations": [ +- { +- "name": "download", +- "object": "gridfsbucket", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "error": true +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- } +- ] +-} +--- a/src/libmongoc/tests/json/retryable_reads/legacy/gridfs-downloadByName-serverErrors.json ++++ /dev/null +@@ -1,849 +0,0 @@ +-{ +- "runOn": [ +- { +- "minServerVersion": "4.0", +- "topology": [ +- "single", +- "replicaset" +- ] +- }, +- { +- "minServerVersion": "4.1.7", +- "topology": [ +- "sharded", +- "load-balanced" +- ] +- } +- ], +- "database_name": "retryable-reads-tests", +- "bucket_name": "fs", +- "data": { +- "fs.files": [ +- { +- "_id": { +- "$oid": "000000000000000000000001" +- }, +- "length": 1, +- "chunkSize": 4, +- "uploadDate": { +- "$date": "1970-01-01T00:00:00.000Z" +- }, +- "filename": "abc", +- "metadata": {} +- } +- ], +- "fs.chunks": [ +- { +- "_id": { +- "$oid": "000000000000000000000002" +- }, +- "files_id": { +- "$oid": "000000000000000000000001" +- }, +- "n": 0, +- "data": { +- "$binary": { +- "base64": "EQ==", +- "subType": "00" +- } +- } +- } +- ] +- }, +- "tests": [ +- { +- "description": "DownloadByName succeeds after InterruptedAtShutdown", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 11600 +- } +- }, +- "operations": [ +- { +- "name": "download_by_name", +- "object": "gridfsbucket", +- "arguments": { +- "filename": "abc" +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "DownloadByName succeeds after InterruptedDueToReplStateChange", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 11602 +- } +- }, +- "operations": [ +- { +- "name": "download_by_name", +- "object": "gridfsbucket", +- "arguments": { +- "filename": "abc" +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "DownloadByName succeeds after NotWritablePrimary", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 10107 +- } +- }, +- "operations": [ +- { +- "name": "download_by_name", +- "object": "gridfsbucket", +- "arguments": { +- "filename": "abc" +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "DownloadByName succeeds after NotPrimaryNoSecondaryOk", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 13435 +- } +- }, +- "operations": [ +- { +- "name": "download_by_name", +- "object": "gridfsbucket", +- "arguments": { +- "filename": "abc" +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "DownloadByName succeeds after NotPrimaryOrSecondary", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 13436 +- } +- }, +- "operations": [ +- { +- "name": "download_by_name", +- "object": "gridfsbucket", +- "arguments": { +- "filename": "abc" +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "DownloadByName succeeds after PrimarySteppedDown", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 189 +- } +- }, +- "operations": [ +- { +- "name": "download_by_name", +- "object": "gridfsbucket", +- "arguments": { +- "filename": "abc" +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "DownloadByName succeeds after ShutdownInProgress", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 91 +- } +- }, +- "operations": [ +- { +- "name": "download_by_name", +- "object": "gridfsbucket", +- "arguments": { +- "filename": "abc" +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "DownloadByName succeeds after HostNotFound", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 7 +- } +- }, +- "operations": [ +- { +- "name": "download_by_name", +- "object": "gridfsbucket", +- "arguments": { +- "filename": "abc" +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "DownloadByName succeeds after HostUnreachable", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 6 +- } +- }, +- "operations": [ +- { +- "name": "download_by_name", +- "object": "gridfsbucket", +- "arguments": { +- "filename": "abc" +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "DownloadByName succeeds after NetworkTimeout", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 89 +- } +- }, +- "operations": [ +- { +- "name": "download_by_name", +- "object": "gridfsbucket", +- "arguments": { +- "filename": "abc" +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "DownloadByName succeeds after SocketException", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 9001 +- } +- }, +- "operations": [ +- { +- "name": "download_by_name", +- "object": "gridfsbucket", +- "arguments": { +- "filename": "abc" +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "DownloadByName fails after two NotWritablePrimary errors", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 2 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 10107 +- } +- }, +- "operations": [ +- { +- "name": "download_by_name", +- "object": "gridfsbucket", +- "arguments": { +- "filename": "abc" +- }, +- "error": true +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "DownloadByName fails after NotWritablePrimary when retryReads is false", +- "clientOptions": { +- "retryReads": false +- }, +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "errorCode": 10107 +- } +- }, +- "operations": [ +- { +- "name": "download_by_name", +- "object": "gridfsbucket", +- "arguments": { +- "filename": "abc" +- }, +- "error": true +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- } +- ] +-} +--- a/src/libmongoc/tests/json/retryable_reads/legacy/gridfs-downloadByName.json ++++ /dev/null +@@ -1,250 +0,0 @@ +-{ +- "runOn": [ +- { +- "minServerVersion": "4.0", +- "topology": [ +- "single", +- "replicaset" +- ] +- }, +- { +- "minServerVersion": "4.1.7", +- "topology": [ +- "sharded", +- "load-balanced" +- ] +- } +- ], +- "database_name": "retryable-reads-tests", +- "bucket_name": "fs", +- "data": { +- "fs.files": [ +- { +- "_id": { +- "$oid": "000000000000000000000001" +- }, +- "length": 1, +- "chunkSize": 4, +- "uploadDate": { +- "$date": "1970-01-01T00:00:00.000Z" +- }, +- "filename": "abc", +- "metadata": {} +- } +- ], +- "fs.chunks": [ +- { +- "_id": { +- "$oid": "000000000000000000000002" +- }, +- "files_id": { +- "$oid": "000000000000000000000001" +- }, +- "n": 0, +- "data": { +- "$binary": { +- "base64": "EQ==", +- "subType": "00" +- } +- } +- } +- ] +- }, +- "tests": [ +- { +- "description": "DownloadByName succeeds on first attempt", +- "operations": [ +- { +- "name": "download_by_name", +- "object": "gridfsbucket", +- "arguments": { +- "filename": "abc" +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "DownloadByName succeeds on second attempt", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "closeConnection": true +- } +- }, +- "operations": [ +- { +- "name": "download_by_name", +- "object": "gridfsbucket", +- "arguments": { +- "filename": "abc" +- } +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.chunks", +- "filter": { +- "files_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "sort": { +- "n": 1 +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "DownloadByName fails on first attempt", +- "clientOptions": { +- "retryReads": false +- }, +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 1 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "closeConnection": true +- } +- }, +- "operations": [ +- { +- "name": "download_by_name", +- "object": "gridfsbucket", +- "arguments": { +- "filename": "abc" +- }, +- "error": true +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- }, +- { +- "description": "DownloadByName fails on second attempt", +- "failPoint": { +- "configureFailPoint": "failCommand", +- "mode": { +- "times": 2 +- }, +- "data": { +- "failCommands": [ +- "find" +- ], +- "closeConnection": true +- } +- }, +- "operations": [ +- { +- "name": "download_by_name", +- "object": "gridfsbucket", +- "arguments": { +- "filename": "abc" +- }, +- "error": true +- } +- ], +- "expectations": [ +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- }, +- { +- "command_started_event": { +- "command": { +- "find": "fs.files", +- "filter": { +- "filename": "abc" +- } +- }, +- "database_name": "retryable-reads-tests" +- } +- } +- ] +- } +- ] +-} +--- /dev/null ++++ b/src/libmongoc/tests/json/retryable_reads/unified/gridfs-download-serverErrors.json +@@ -0,0 +1,1142 @@ ++{ ++ "description": "gridfs-download-serverErrors", ++ "schemaVersion": "1.9", ++ "runOnRequirements": [ ++ { ++ "minServerVersion": "4.0", ++ "topologies": [ ++ "single", ++ "replicaset" ++ ] ++ }, ++ { ++ "minServerVersion": "4.1.7", ++ "topologies": [ ++ "sharded", ++ "load-balanced" ++ ] ++ } ++ ], ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "useMultipleMongoses": false, ++ "observeEvents": [ ++ "commandStartedEvent" ++ ] ++ } ++ }, ++ { ++ "database": { ++ "id": "database0", ++ "client": "client0", ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "bucket": { ++ "id": "bucket0", ++ "database": "database0" ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "collectionName": "fs.files", ++ "databaseName": "retryable-reads-tests", ++ "documents": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "length": 1, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "filename": "abc", ++ "metadata": {} ++ } ++ ] ++ }, ++ { ++ "collectionName": "fs.chunks", ++ "databaseName": "retryable-reads-tests", ++ "documents": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000002" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "n": 0, ++ "data": { ++ "$binary": { ++ "base64": "EQ==", ++ "subType": "00" ++ } ++ } ++ } ++ ] ++ } ++ ], ++ "tests": [ ++ { ++ "description": "Download succeeds after InterruptedAtShutdown", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 11600 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "Download succeeds after InterruptedDueToReplStateChange", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 11602 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "Download succeeds after NotWritablePrimary", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 10107 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "Download succeeds after NotPrimaryNoSecondaryOk", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 13435 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "Download succeeds after NotPrimaryOrSecondary", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 13436 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "Download succeeds after PrimarySteppedDown", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 189 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "Download succeeds after ShutdownInProgress", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 91 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "Download succeeds after HostNotFound", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 7 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "Download succeeds after HostUnreachable", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 6 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "Download succeeds after NetworkTimeout", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 89 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "Download succeeds after SocketException", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 9001 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "Download fails after two NotWritablePrimary errors", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 2 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 10107 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ }, ++ "expectError": { ++ "isError": true ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "Download fails after NotWritablePrimary when retryReads is false", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "createEntities", ++ "arguments": { ++ "entities": [ ++ { ++ "client": { ++ "id": "client1", ++ "useMultipleMongoses": false, ++ "uriOptions": { ++ "retryReads": false ++ }, ++ "observeEvents": [ ++ "commandStartedEvent" ++ ] ++ } ++ }, ++ { ++ "database": { ++ "id": "database1", ++ "client": "client1", ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "bucket": { ++ "id": "bucket1", ++ "database": "database1" ++ } ++ } ++ ] ++ } ++ }, ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client1", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 10107 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket1", ++ "name": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ }, ++ "expectError": { ++ "isError": true ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client1", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ } ++ ] ++} +--- /dev/null ++++ b/src/libmongoc/tests/json/retryable_reads/unified/gridfs-download.json +@@ -0,0 +1,379 @@ ++{ ++ "description": "gridfs-download", ++ "schemaVersion": "1.9", ++ "runOnRequirements": [ ++ { ++ "minServerVersion": "4.0", ++ "topologies": [ ++ "single", ++ "replicaset" ++ ] ++ }, ++ { ++ "minServerVersion": "4.1.7", ++ "topologies": [ ++ "sharded", ++ "load-balanced" ++ ] ++ } ++ ], ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "useMultipleMongoses": false, ++ "observeEvents": [ ++ "commandStartedEvent" ++ ] ++ } ++ }, ++ { ++ "database": { ++ "id": "database0", ++ "client": "client0", ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "bucket": { ++ "id": "bucket0", ++ "database": "database0" ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "collectionName": "fs.files", ++ "databaseName": "retryable-reads-tests", ++ "documents": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "length": 1, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "filename": "abc", ++ "metadata": {} ++ } ++ ] ++ }, ++ { ++ "collectionName": "fs.chunks", ++ "databaseName": "retryable-reads-tests", ++ "documents": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000002" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "n": 0, ++ "data": { ++ "$binary": { ++ "base64": "EQ==", ++ "subType": "00" ++ } ++ } ++ } ++ ] ++ } ++ ], ++ "tests": [ ++ { ++ "description": "Download succeeds on first attempt", ++ "operations": [ ++ { ++ "object": "bucket0", ++ "name": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "Download succeeds on second attempt", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "closeConnection": true ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "Download fails on first attempt", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "createEntities", ++ "arguments": { ++ "entities": [ ++ { ++ "client": { ++ "id": "client1", ++ "useMultipleMongoses": false, ++ "uriOptions": { ++ "retryReads": false ++ }, ++ "observeEvents": [ ++ "commandStartedEvent" ++ ] ++ } ++ }, ++ { ++ "database": { ++ "id": "database1", ++ "client": "client1", ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "bucket": { ++ "id": "bucket1", ++ "database": "database1" ++ } ++ } ++ ] ++ } ++ }, ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client1", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "closeConnection": true ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket1", ++ "name": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ }, ++ "expectError": { ++ "isError": true ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client1", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "Download fails on second attempt", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 2 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "closeConnection": true ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ }, ++ "expectError": { ++ "isError": true ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "_id": { ++ "$eq": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ } ++ ] ++} +--- /dev/null ++++ b/src/libmongoc/tests/json/retryable_reads/unified/gridfs-downloadByName-serverErrors.json +@@ -0,0 +1,1016 @@ ++{ ++ "description": "gridfs-downloadByName-serverErrors", ++ "schemaVersion": "1.9", ++ "runOnRequirements": [ ++ { ++ "minServerVersion": "4.0", ++ "topologies": [ ++ "single", ++ "replicaset" ++ ] ++ }, ++ { ++ "minServerVersion": "4.1.7", ++ "topologies": [ ++ "sharded", ++ "load-balanced" ++ ] ++ } ++ ], ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "useMultipleMongoses": false, ++ "observeEvents": [ ++ "commandStartedEvent" ++ ] ++ } ++ }, ++ { ++ "database": { ++ "id": "database0", ++ "client": "client0", ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "bucket": { ++ "id": "bucket0", ++ "database": "database0" ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "collectionName": "fs.files", ++ "databaseName": "retryable-reads-tests", ++ "documents": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "length": 1, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "filename": "abc", ++ "metadata": {} ++ } ++ ] ++ }, ++ { ++ "collectionName": "fs.chunks", ++ "databaseName": "retryable-reads-tests", ++ "documents": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000002" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "n": 0, ++ "data": { ++ "$binary": { ++ "base64": "EQ==", ++ "subType": "00" ++ } ++ } ++ } ++ ] ++ } ++ ], ++ "tests": [ ++ { ++ "description": "DownloadByName succeeds after InterruptedAtShutdown", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 11600 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "downloadByName", ++ "arguments": { ++ "filename": "abc" ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "DownloadByName succeeds after InterruptedDueToReplStateChange", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 11602 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "downloadByName", ++ "arguments": { ++ "filename": "abc" ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "DownloadByName succeeds after NotWritablePrimary", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 10107 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "downloadByName", ++ "arguments": { ++ "filename": "abc" ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "DownloadByName succeeds after NotPrimaryNoSecondaryOk", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 13435 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "downloadByName", ++ "arguments": { ++ "filename": "abc" ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "DownloadByName succeeds after NotPrimaryOrSecondary", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 13436 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "downloadByName", ++ "arguments": { ++ "filename": "abc" ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "DownloadByName succeeds after PrimarySteppedDown", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 189 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "downloadByName", ++ "arguments": { ++ "filename": "abc" ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "DownloadByName succeeds after ShutdownInProgress", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 91 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "downloadByName", ++ "arguments": { ++ "filename": "abc" ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "DownloadByName succeeds after HostNotFound", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 7 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "downloadByName", ++ "arguments": { ++ "filename": "abc" ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "DownloadByName succeeds after HostUnreachable", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 6 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "downloadByName", ++ "arguments": { ++ "filename": "abc" ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "DownloadByName succeeds after NetworkTimeout", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 89 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "downloadByName", ++ "arguments": { ++ "filename": "abc" ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "DownloadByName succeeds after SocketException", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 9001 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "downloadByName", ++ "arguments": { ++ "filename": "abc" ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "DownloadByName fails after two NotWritablePrimary errors", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 2 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 10107 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "downloadByName", ++ "arguments": { ++ "filename": "abc" ++ }, ++ "expectError": { ++ "isError": true ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "DownloadByName fails after NotWritablePrimary when retryReads is false", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "createEntities", ++ "arguments": { ++ "entities": [ ++ { ++ "client": { ++ "id": "client1", ++ "useMultipleMongoses": false, ++ "uriOptions": { ++ "retryReads": false ++ }, ++ "observeEvents": [ ++ "commandStartedEvent" ++ ] ++ } ++ }, ++ { ++ "database": { ++ "id": "database1", ++ "client": "client1", ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "bucket": { ++ "id": "bucket1", ++ "database": "database1" ++ } ++ } ++ ] ++ } ++ }, ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client1", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorCode": 10107 ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket1", ++ "name": "downloadByName", ++ "arguments": { ++ "filename": "abc" ++ }, ++ "expectError": { ++ "isError": true ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client1", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ } ++ ] ++} +--- /dev/null ++++ b/src/libmongoc/tests/json/retryable_reads/unified/gridfs-downloadByName.json +@@ -0,0 +1,347 @@ ++{ ++ "description": "gridfs-downloadByName", ++ "schemaVersion": "1.9", ++ "runOnRequirements": [ ++ { ++ "minServerVersion": "4.0", ++ "topologies": [ ++ "single", ++ "replicaset" ++ ] ++ }, ++ { ++ "minServerVersion": "4.1.7", ++ "topologies": [ ++ "sharded", ++ "load-balanced" ++ ] ++ } ++ ], ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "useMultipleMongoses": false, ++ "observeEvents": [ ++ "commandStartedEvent" ++ ] ++ } ++ }, ++ { ++ "database": { ++ "id": "database0", ++ "client": "client0", ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "bucket": { ++ "id": "bucket0", ++ "database": "database0" ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "collectionName": "fs.files", ++ "databaseName": "retryable-reads-tests", ++ "documents": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "length": 1, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "filename": "abc", ++ "metadata": {} ++ } ++ ] ++ }, ++ { ++ "collectionName": "fs.chunks", ++ "databaseName": "retryable-reads-tests", ++ "documents": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000002" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "n": 0, ++ "data": { ++ "$binary": { ++ "base64": "EQ==", ++ "subType": "00" ++ } ++ } ++ } ++ ] ++ } ++ ], ++ "tests": [ ++ { ++ "description": "DownloadByName succeeds on first attempt", ++ "operations": [ ++ { ++ "object": "bucket0", ++ "name": "downloadByName", ++ "arguments": { ++ "filename": "abc" ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "DownloadByName succeeds on second attempt", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "closeConnection": true ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "downloadByName", ++ "arguments": { ++ "filename": "abc" ++ }, ++ "expectResult": { ++ "$$matchesHexBytes": "11" ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.chunks" ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "DownloadByName fails on first attempt", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "createEntities", ++ "arguments": { ++ "entities": [ ++ { ++ "client": { ++ "id": "client1", ++ "useMultipleMongoses": false, ++ "uriOptions": { ++ "retryReads": false ++ }, ++ "observeEvents": [ ++ "commandStartedEvent" ++ ] ++ } ++ }, ++ { ++ "database": { ++ "id": "database1", ++ "client": "client1", ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "bucket": { ++ "id": "bucket1", ++ "database": "database1" ++ } ++ } ++ ] ++ } ++ }, ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client1", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "closeConnection": true ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket1", ++ "name": "downloadByName", ++ "arguments": { ++ "filename": "abc" ++ }, ++ "expectError": { ++ "isError": true ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client1", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "DownloadByName fails on second attempt", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 2 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "closeConnection": true ++ } ++ } ++ } ++ }, ++ { ++ "object": "bucket0", ++ "name": "downloadByName", ++ "arguments": { ++ "filename": "abc" ++ }, ++ "expectError": { ++ "isError": true ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "fs.files", ++ "filter": { ++ "filename": "abc" ++ } ++ }, ++ "databaseName": "retryable-reads-tests" ++ } ++ } ++ ] ++ } ++ ] ++ } ++ ] ++} +--- a/src/libmongoc/tests/test-mongoc-gridfs-bucket.c ++++ b/src/libmongoc/tests/test-mongoc-gridfs-bucket.c +@@ -823,7 +823,7 @@ + static void + test_all_spec_tests (TestSuite *suite) + { +- install_json_test_suite (suite, JSON_DIR, "gridfs", &test_gridfs_cb); ++ install_json_test_suite (suite, JSON_DIR, "gridfs/legacy", &test_gridfs_cb); + } + + static void +@@ -1051,6 +1051,74 @@ + mongoc_client_destroy (client); + } + ++// GridFS prose test 1: "Aborting an upload with an injected file ID does not delete other files' chunks". ++static void ++prose_test_1 (void *unused) ++{ ++ BSON_UNUSED (unused); ++ ++ mongoc_client_t *client = test_framework_new_default_client (); ++ mongoc_database_t *db = mongoc_client_get_database (client, "db"); ++ ++ bson_error_t error; ++ mongoc_gridfs_bucket_t *bucket = mongoc_gridfs_bucket_new (db, NULL, NULL, &error); ++ ASSERT_OR_PRINT (bucket, error); ++ ++ // Clear the bucket's contents. Errors (e.g. "ns not found") are ignored. ++ (void) mongoc_collection_drop_with_opts (bucket->files, NULL, NULL); ++ (void) mongoc_collection_drop_with_opts (bucket->chunks, NULL, NULL); ++ ++ // Upload "file1". ++ static const char *const file1_bytes = "file1 contents"; ++ const size_t file1_len = strlen (file1_bytes); ++ bson_value_t file1_id; ++ _upload_file_from_str (bucket, "file1", file1_bytes, NULL, &file1_id); ++ ++ // Open an upload stream for "file2" with a file ID that is a query operator ++ // document, then write to it and abort. ++ { ++ bson_t *const injected = tmp_bson ("{'$gt': {'$minKey': 1}}"); ++ const bson_value_t file2_id = {.value_type = BSON_TYPE_DOCUMENT, ++ .value.v_doc.data = (uint8_t *) bson_get_data (injected), ++ .value.v_doc.data_len = injected->len}; ++ ++ bson_t *const opts = tmp_bson ("{'chunkSizeBytes': 2}"); ++ mongoc_stream_t *const up = ++ mongoc_gridfs_bucket_open_upload_stream_with_id (bucket, &file2_id, "file2", opts, &error); ++ ASSERT_OR_PRINT (up, error); ++ ++ ASSERT_CMPSSIZE_T (mongoc_stream_write (up, (void *) "data", 4u, 0), ==, 4); ++ ASSERT_OR_PRINT (mongoc_gridfs_bucket_abort_upload (up), (mongoc_gridfs_bucket_stream_error (up, &error), error)); ++ mongoc_stream_destroy (up); ++ } ++ ++ // Expect "file1" is still readable: its chunks were not deleted by the abort. ++ { ++ char buf[100] = {0}; ++ const size_t nread = _download_file_into_buf (bucket, &file1_id, buf, sizeof buf); ++ ASSERT_CMPSIZE_T (nread, ==, file1_len); ++ ASSERT_CMPSTR (buf, file1_bytes); ++ } ++ ++ // Expect "file2" was not saved. ++ { ++ bson_t *const injected = tmp_bson ("{'$gt': {'$minKey': 1}}"); ++ const bson_value_t file2_id = {.value_type = BSON_TYPE_DOCUMENT, ++ .value.v_doc.data = (uint8_t *) bson_get_data (injected), ++ .value.v_doc.data_len = injected->len}; ++ ++ mongoc_stream_t *const down = mongoc_gridfs_bucket_open_download_stream (bucket, &file2_id, &error); ++ ASSERT (!down); ++ ASSERT_ERROR_CONTAINS ( ++ error, MONGOC_ERROR_GRIDFS, MONGOC_ERROR_GRIDFS_BUCKET_FILE_NOT_FOUND, "No file with given id exists"); ++ } ++ ++ bson_value_destroy (&file1_id); ++ mongoc_gridfs_bucket_destroy (bucket); ++ mongoc_database_destroy (db); ++ mongoc_client_destroy (client); ++} ++ + void + test_gridfs_bucket_install (TestSuite *suite) + { +@@ -1073,4 +1141,11 @@ + test_framework_skip_if_no_sessions, + test_framework_skip_if_no_crypto); + TestSuite_AddLive (suite, "/gridfs/options", test_gridfs_bucket_opts); ++ TestSuite_AddFull (suite, ++ "/gridfs/prose_test_1", ++ prose_test_1, ++ NULL, ++ NULL, ++ // Server versions older than 5.0 do not support document values with "$"-prefixed keys. ++ test_framework_skip_if_max_wire_version_less_than_13); + } +--- a/src/libmongoc/tests/test-mongoc-gridfs.c ++++ b/src/libmongoc/tests/test-mongoc-gridfs.c +@@ -249,6 +249,60 @@ + } + + ++// Test that a file ID containing query operators is matched exactly, and does ++// not remove other files. Regression test for CDRIVER-6427. ++static void ++test_remove_operator_injection (void) ++{ ++ mongoc_client_t *client = test_framework_new_default_client (); ++ bson_error_t error; ++ mongoc_gridfs_t *gridfs; ++ ++ ASSERT_OR_PRINT (gridfs = get_test_gridfs (client, "operator_injection", &error), error); ++ drop_collections (gridfs, NULL); // Ignore error from "ns not found". ++ ++ // Create two files. ++ for (int i = 0; i < 2; i++) { ++ mongoc_gridfs_file_t *file = mongoc_gridfs_create_file (gridfs, NULL); ++ ASSERT (file); ++ ++ const mongoc_iovec_t iov = {.iov_base = (void *) "data", .iov_len = 4}; ++ ASSERT_CMPSSIZE_T (mongoc_gridfs_file_writev (file, &iov, 1, 0), ==, 4); ++ ASSERT (mongoc_gridfs_file_save (file)); ++ mongoc_gridfs_file_destroy (file); ++ } ++ ++ // Attempt to remove with a file ID that is an operator document. ++ { ++ mongoc_gridfs_file_t *file = mongoc_gridfs_create_file (gridfs, NULL); ++ ASSERT (file); ++ ++ // `{"$ne": null}` matches every file if interpreted as an operator. ++ bson_t *injected = tmp_bson ("{'$ne': null}"); ++ const bson_value_t id = {.value_type = BSON_TYPE_DOCUMENT, ++ .value.v_doc.data = (uint8_t *) bson_get_data (injected), ++ .value.v_doc.data_len = injected->len}; ++ ASSERT_OR_PRINT (mongoc_gridfs_file_set_id (file, &id, &error), error); ++ ++ // Expect no match. Ignore possible error. Server 8.2+ returns an error trying to match `_id` to a $-prefixed key. ++ (void) mongoc_gridfs_file_remove (file, &error); ++ ++ mongoc_gridfs_file_destroy (file); ++ } ++ ++ // Expect both files remain. ++ { ++ int64_t count = mongoc_collection_count_documents ( ++ mongoc_gridfs_get_files (gridfs), tmp_bson ("{}"), NULL, NULL, NULL, &error); ++ ASSERT_OR_PRINT (count != -1, error); ++ ASSERT_CMPINT64 (count, ==, 2); ++ } ++ ++ mongoc_gridfs_destroy (gridfs); ++ mongoc_client_destroy (client); ++} ++ ++ + static void + prep_files (mongoc_gridfs_t *gridfs) + { +@@ -1375,14 +1429,14 @@ + request = mock_server_receives_msg (server, + MONGOC_MSG_NONE, + tmp_bson ("{'$db': 'db', 'delete': 'fs.files', 'writeConcern': {'w': 2}}"), +- tmp_bson ("{'q': {'_id': 1}, 'limit': 1}")); ++ tmp_bson ("{'q': {'_id': {'$eq': 1}}, 'limit': 1}")); + + reply_to_request_with_ok_and_destroy (request); + + request = mock_server_receives_msg (server, + MONGOC_MSG_NONE, + tmp_bson ("{'$db': 'db', 'delete': 'fs.chunks', 'writeConcern': {'w': 2}}"), +- tmp_bson ("{'q': {'files_id': 1}, 'limit': 0}")); ++ tmp_bson ("{'q': {'files_id': {'$eq': 1}}, 'limit': 0}")); + + reply_to_request_with_ok_and_destroy (request); + ASSERT (future_get_bool (future)); +@@ -1621,6 +1675,7 @@ + TestSuite_AddLive (suite, "/gridfs_old/seek", test_seek); + TestSuite_AddLive (suite, "/gridfs_old/stream", test_stream); + TestSuite_AddLive (suite, "/gridfs_old/remove", test_remove); ++ TestSuite_AddLive (suite, "/gridfs_old/remove/operator_injection", test_remove_operator_injection); + TestSuite_AddLive (suite, "/gridfs_old/write", test_write); + TestSuite_AddLive (suite, "/gridfs_old/write_at_boundary", test_write_at_boundary); + TestSuite_AddLive (suite, "/gridfs_old/write_past_end", test_write_past_end); +--- a/src/libmongoc/tests/unified/runner.c ++++ b/src/libmongoc/tests/unified/runner.c +@@ -40,6 +40,15 @@ + // CDRIVER-4001, DRIVERS-1781, and DRIVERS-1448: 5.0 cursor behavior + {"poc-command-monitoring", "A successful find event with a getmore and the server kills the cursor"}, + ++ // libmongoc does not implement the optional advanced GridFS API, so it has no ++ // "download by name" helper. ++ {"gridfs-downloadByName", SKIP_ALL_TESTS}, ++ {"gridfs-downloadByName-serverErrors", SKIP_ALL_TESTS}, ++ ++ // CDRIVER-5782: libmongoc does not have a GridFS bucket rename helper. ++ {"gridfs-queriesUseEq", "rename uses $eq to update the files collection document"}, ++ {"gridfs-queriesUseEq", "rename with a file id containing a query operator does not rename any files"}, ++ + // libmongoc does not have a distinct helper, so skip snapshot tests testing particular distinct functionality + {"snapshot-sessions", "Distinct operation with snapshot"}, + {"snapshot-sessions", "Mixed operation with snapshot"}, +@@ -2197,6 +2206,8 @@ + { + run_unified_tests (suite, JSON_DIR, "unified"); + ++ run_unified_tests (suite, JSON_DIR, "gridfs/unified"); ++ + run_unified_tests (suite, JSON_DIR, "crud/unified"); + + run_unified_tests (suite, JSON_DIR, "transactions/unified"); +--- a/src/libmongoc/tests/json/gridfs/delete.json ++++ /dev/null +@@ -1,291 +0,0 @@ +-{ +- "data": { +- "files": [ +- { +- "_id": { +- "$oid": "000000000000000000000001" +- }, +- "length": 0, +- "chunkSize": 4, +- "uploadDate": { +- "$date": "1970-01-01T00:00:00.000Z" +- }, +- "md5": "d41d8cd98f00b204e9800998ecf8427e", +- "filename": "length-0", +- "contentType": "application/octet-stream", +- "aliases": [], +- "metadata": {} +- }, +- { +- "_id": { +- "$oid": "000000000000000000000002" +- }, +- "length": 0, +- "chunkSize": 4, +- "uploadDate": { +- "$date": "1970-01-01T00:00:00.000Z" +- }, +- "md5": "d41d8cd98f00b204e9800998ecf8427e", +- "filename": "length-0-with-empty-chunk", +- "contentType": "application/octet-stream", +- "aliases": [], +- "metadata": {} +- }, +- { +- "_id": { +- "$oid": "000000000000000000000003" +- }, +- "length": 2, +- "chunkSize": 4, +- "uploadDate": { +- "$date": "1970-01-01T00:00:00.000Z" +- }, +- "md5": "c700ed4fdb1d27055aa3faa2c2432283", +- "filename": "length-2", +- "contentType": "application/octet-stream", +- "aliases": [], +- "metadata": {} +- }, +- { +- "_id": { +- "$oid": "000000000000000000000004" +- }, +- "length": 8, +- "chunkSize": 4, +- "uploadDate": { +- "$date": "1970-01-01T00:00:00.000Z" +- }, +- "md5": "dd254cdc958e53abaa67da9f797125f5", +- "filename": "length-8", +- "contentType": "application/octet-stream", +- "aliases": [], +- "metadata": {} +- } +- ], +- "chunks": [ +- { +- "_id": { +- "$oid": "000000000000000000000001" +- }, +- "files_id": { +- "$oid": "000000000000000000000002" +- }, +- "n": 0, +- "data": { +- "$hex": "" +- } +- }, +- { +- "_id": { +- "$oid": "000000000000000000000002" +- }, +- "files_id": { +- "$oid": "000000000000000000000003" +- }, +- "n": 0, +- "data": { +- "$hex": "1122" +- } +- }, +- { +- "_id": { +- "$oid": "000000000000000000000003" +- }, +- "files_id": { +- "$oid": "000000000000000000000004" +- }, +- "n": 0, +- "data": { +- "$hex": "11223344" +- } +- }, +- { +- "_id": { +- "$oid": "000000000000000000000004" +- }, +- "files_id": { +- "$oid": "000000000000000000000004" +- }, +- "n": 1, +- "data": { +- "$hex": "55667788" +- } +- } +- ] +- }, +- "tests": [ +- { +- "description": "Delete when length is 0", +- "act": { +- "operation": "delete", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000001" +- } +- } +- }, +- "assert": { +- "result": "void", +- "data": [ +- { +- "delete": "expected.files", +- "deletes": [ +- { +- "q": { +- "_id": { +- "$oid": "000000000000000000000001" +- } +- }, +- "limit": 1 +- } +- ] +- } +- ] +- } +- }, +- { +- "description": "Delete when length is 0 and there is one extra empty chunk", +- "act": { +- "operation": "delete", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000002" +- } +- } +- }, +- "assert": { +- "result": "void", +- "data": [ +- { +- "delete": "expected.files", +- "deletes": [ +- { +- "q": { +- "_id": { +- "$oid": "000000000000000000000002" +- } +- }, +- "limit": 1 +- } +- ] +- }, +- { +- "delete": "expected.chunks", +- "deletes": [ +- { +- "q": { +- "files_id": { +- "$oid": "000000000000000000000002" +- } +- }, +- "limit": 0 +- } +- ] +- } +- ] +- } +- }, +- { +- "description": "Delete when length is 8", +- "act": { +- "operation": "delete", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000004" +- } +- } +- }, +- "assert": { +- "result": "void", +- "data": [ +- { +- "delete": "expected.files", +- "deletes": [ +- { +- "q": { +- "_id": { +- "$oid": "000000000000000000000004" +- } +- }, +- "limit": 1 +- } +- ] +- }, +- { +- "delete": "expected.chunks", +- "deletes": [ +- { +- "q": { +- "files_id": { +- "$oid": "000000000000000000000004" +- } +- }, +- "limit": 0 +- } +- ] +- } +- ] +- } +- }, +- { +- "description": "Delete when files entry does not exist", +- "act": { +- "operation": "delete", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000000" +- } +- } +- }, +- "assert": { +- "error": "FileNotFound" +- } +- }, +- { +- "description": "Delete when files entry does not exist and there are orphaned chunks", +- "arrange": { +- "data": [ +- { +- "delete": "fs.files", +- "deletes": [ +- { +- "q": { +- "_id": { +- "$oid": "000000000000000000000004" +- } +- }, +- "limit": 1 +- } +- ] +- } +- ] +- }, +- "act": { +- "operation": "delete", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000004" +- } +- } +- }, +- "assert": { +- "error": "FileNotFound", +- "data": [ +- { +- "delete": "expected.files", +- "deletes": [ +- { +- "q": { +- "_id": { +- "$oid": "000000000000000000000004" +- } +- }, +- "limit": 1 +- } +- ] +- } +- ] +- } +- } +- ] +-} +--- a/src/libmongoc/tests/json/gridfs/download.json ++++ /dev/null +@@ -1,467 +0,0 @@ +-{ +- "data": { +- "files": [ +- { +- "_id": { +- "$oid": "000000000000000000000001" +- }, +- "length": 0, +- "chunkSize": 4, +- "uploadDate": { +- "$date": "1970-01-01T00:00:00.000Z" +- }, +- "md5": "d41d8cd98f00b204e9800998ecf8427e", +- "filename": "length-0", +- "contentType": "application/octet-stream", +- "aliases": [], +- "metadata": {} +- }, +- { +- "_id": { +- "$oid": "000000000000000000000002" +- }, +- "length": 0, +- "chunkSize": 4, +- "uploadDate": { +- "$date": "1970-01-01T00:00:00.000Z" +- }, +- "md5": "d41d8cd98f00b204e9800998ecf8427e", +- "filename": "length-0-with-empty-chunk", +- "contentType": "application/octet-stream", +- "aliases": [], +- "metadata": {} +- }, +- { +- "_id": { +- "$oid": "000000000000000000000003" +- }, +- "length": 2, +- "chunkSize": 4, +- "uploadDate": { +- "$date": "1970-01-01T00:00:00.000Z" +- }, +- "md5": "c700ed4fdb1d27055aa3faa2c2432283", +- "filename": "length-2", +- "contentType": "application/octet-stream", +- "aliases": [], +- "metadata": {} +- }, +- { +- "_id": { +- "$oid": "000000000000000000000004" +- }, +- "length": 8, +- "chunkSize": 4, +- "uploadDate": { +- "$date": "1970-01-01T00:00:00.000Z" +- }, +- "md5": "dd254cdc958e53abaa67da9f797125f5", +- "filename": "length-8", +- "contentType": "application/octet-stream", +- "aliases": [], +- "metadata": {} +- }, +- { +- "_id": { +- "$oid": "000000000000000000000005" +- }, +- "length": 10, +- "chunkSize": 4, +- "uploadDate": { +- "$date": "1970-01-01T00:00:00.000Z" +- }, +- "md5": "57d83cd477bfb1ccd975ab33d827a92b", +- "filename": "length-10", +- "contentType": "application/octet-stream", +- "aliases": [], +- "metadata": {} +- }, +- { +- "_id": { +- "$oid": "000000000000000000000006" +- }, +- "length": 2, +- "chunkSize": 4, +- "uploadDate": { +- "$date": "1970-01-01T00:00:00.000Z" +- }, +- "md5": "c700ed4fdb1d27055aa3faa2c2432283", +- "contentType": "application/octet-stream", +- "aliases": [], +- "metadata": {} +- } +- ], +- "chunks": [ +- { +- "_id": { +- "$oid": "000000000000000000000001" +- }, +- "files_id": { +- "$oid": "000000000000000000000002" +- }, +- "n": 0, +- "data": { +- "$hex": "" +- } +- }, +- { +- "_id": { +- "$oid": "000000000000000000000002" +- }, +- "files_id": { +- "$oid": "000000000000000000000003" +- }, +- "n": 0, +- "data": { +- "$hex": "1122" +- } +- }, +- { +- "_id": { +- "$oid": "000000000000000000000003" +- }, +- "files_id": { +- "$oid": "000000000000000000000004" +- }, +- "n": 0, +- "data": { +- "$hex": "11223344" +- } +- }, +- { +- "_id": { +- "$oid": "000000000000000000000004" +- }, +- "files_id": { +- "$oid": "000000000000000000000004" +- }, +- "n": 1, +- "data": { +- "$hex": "55667788" +- } +- }, +- { +- "_id": { +- "$oid": "000000000000000000000005" +- }, +- "files_id": { +- "$oid": "000000000000000000000005" +- }, +- "n": 0, +- "data": { +- "$hex": "11223344" +- } +- }, +- { +- "_id": { +- "$oid": "000000000000000000000006" +- }, +- "files_id": { +- "$oid": "000000000000000000000005" +- }, +- "n": 1, +- "data": { +- "$hex": "55667788" +- } +- }, +- { +- "_id": { +- "$oid": "000000000000000000000007" +- }, +- "files_id": { +- "$oid": "000000000000000000000005" +- }, +- "n": 2, +- "data": { +- "$hex": "99aa" +- } +- }, +- { +- "_id": { +- "$oid": "000000000000000000000008" +- }, +- "files_id": { +- "$oid": "000000000000000000000006" +- }, +- "n": 0, +- "data": { +- "$hex": "1122" +- } +- } +- ] +- }, +- "tests": [ +- { +- "description": "Download when length is zero", +- "act": { +- "operation": "download", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000001" +- }, +- "options": {} +- } +- }, +- "assert": { +- "result": { +- "$hex": "" +- } +- } +- }, +- { +- "description": "Download when length is zero and there is one empty chunk", +- "act": { +- "operation": "download", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000002" +- }, +- "options": {} +- } +- }, +- "assert": { +- "result": { +- "$hex": "" +- } +- } +- }, +- { +- "description": "Download when there is one chunk", +- "act": { +- "operation": "download", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000003" +- }, +- "options": {} +- } +- }, +- "assert": { +- "result": { +- "$hex": "1122" +- } +- } +- }, +- { +- "description": "Download when there are two chunks", +- "act": { +- "operation": "download", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000004" +- }, +- "options": {} +- } +- }, +- "assert": { +- "result": { +- "$hex": "1122334455667788" +- } +- } +- }, +- { +- "description": "Download when there are three chunks", +- "act": { +- "operation": "download", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000005" +- }, +- "options": {} +- } +- }, +- "assert": { +- "result": { +- "$hex": "112233445566778899aa" +- } +- } +- }, +- { +- "description": "Download when files entry does not exist", +- "act": { +- "operation": "download", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000000" +- }, +- "options": {} +- } +- }, +- "assert": { +- "error": "FileNotFound" +- } +- }, +- { +- "description": "Download when an intermediate chunk is missing", +- "arrange": { +- "data": [ +- { +- "delete": "fs.chunks", +- "deletes": [ +- { +- "q": { +- "files_id": { +- "$oid": "000000000000000000000005" +- }, +- "n": 1 +- }, +- "limit": 1 +- } +- ] +- } +- ] +- }, +- "act": { +- "operation": "download", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000005" +- } +- } +- }, +- "assert": { +- "error": "ChunkIsMissing" +- } +- }, +- { +- "description": "Download when final chunk is missing", +- "arrange": { +- "data": [ +- { +- "delete": "fs.chunks", +- "deletes": [ +- { +- "q": { +- "files_id": { +- "$oid": "000000000000000000000005" +- }, +- "n": 1 +- }, +- "limit": 1 +- } +- ] +- } +- ] +- }, +- "act": { +- "operation": "download", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000005" +- } +- } +- }, +- "assert": { +- "error": "ChunkIsMissing" +- } +- }, +- { +- "description": "Download when an intermediate chunk is the wrong size", +- "arrange": { +- "data": [ +- { +- "update": "fs.chunks", +- "updates": [ +- { +- "q": { +- "files_id": { +- "$oid": "000000000000000000000005" +- }, +- "n": 1 +- }, +- "u": { +- "$set": { +- "data": { +- "$hex": "556677" +- } +- } +- } +- }, +- { +- "q": { +- "files_id": { +- "$oid": "000000000000000000000005" +- }, +- "n": 2 +- }, +- "u": { +- "$set": { +- "data": { +- "$hex": "8899aa" +- } +- } +- } +- } +- ] +- } +- ] +- }, +- "act": { +- "operation": "download", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000005" +- } +- } +- }, +- "assert": { +- "error": "ChunkIsWrongSize" +- } +- }, +- { +- "description": "Download when final chunk is the wrong size", +- "arrange": { +- "data": [ +- { +- "update": "fs.chunks", +- "updates": [ +- { +- "q": { +- "files_id": { +- "$oid": "000000000000000000000005" +- }, +- "n": 2 +- }, +- "u": { +- "$set": { +- "data": { +- "$hex": "99" +- } +- } +- } +- } +- ] +- } +- ] +- }, +- "act": { +- "operation": "download", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000005" +- } +- } +- }, +- "assert": { +- "error": "ChunkIsWrongSize" +- } +- }, +- { +- "description": "Download legacy file with no name", +- "act": { +- "operation": "download", +- "arguments": { +- "id": { +- "$oid": "000000000000000000000006" +- }, +- "options": {} +- } +- }, +- "assert": { +- "result": { +- "$hex": "1122" +- } +- } +- } +- ] +-} +--- a/src/libmongoc/tests/json/gridfs/download_by_name.json ++++ /dev/null +@@ -1,240 +0,0 @@ +-{ +- "data": { +- "files": [ +- { +- "_id": { +- "$oid": "000000000000000000000001" +- }, +- "length": 1, +- "chunkSize": 4, +- "uploadDate": { +- "$date": "1970-01-01T00:00:00.000Z" +- }, +- "md5": "47ed733b8d10be225eceba344d533586", +- "filename": "abc", +- "contentType": "application/octet-stream", +- "aliases": [], +- "metadata": {} +- }, +- { +- "_id": { +- "$oid": "000000000000000000000002" +- }, +- "length": 1, +- "chunkSize": 4, +- "uploadDate": { +- "$date": "1970-01-02T00:00:00.000Z" +- }, +- "md5": "b15835f133ff2e27c7cb28117bfae8f4", +- "filename": "abc", +- "contentType": "application/octet-stream", +- "aliases": [], +- "metadata": {} +- }, +- { +- "_id": { +- "$oid": "000000000000000000000003" +- }, +- "length": 1, +- "chunkSize": 4, +- "uploadDate": { +- "$date": "1970-01-03T00:00:00.000Z" +- }, +- "md5": "eccbc87e4b5ce2fe28308fd9f2a7baf3", +- "filename": "abc", +- "contentType": "application/octet-stream", +- "aliases": [], +- "metadata": {} +- }, +- { +- "_id": { +- "$oid": "000000000000000000000004" +- }, +- "length": 1, +- "chunkSize": 4, +- "uploadDate": { +- "$date": "1970-01-04T00:00:00.000Z" +- }, +- "md5": "f623e75af30e62bbd73d6df5b50bb7b5", +- "filename": "abc", +- "contentType": "application/octet-stream", +- "aliases": [], +- "metadata": {} +- }, +- { +- "_id": { +- "$oid": "000000000000000000000005" +- }, +- "length": 1, +- "chunkSize": 4, +- "uploadDate": { +- "$date": "1970-01-05T00:00:00.000Z" +- }, +- "md5": "4c614360da93c0a041b22e537de151eb", +- "filename": "abc", +- "contentType": "application/octet-stream", +- "aliases": [], +- "metadata": {} +- } +- ], +- "chunks": [ +- { +- "_id": { +- "$oid": "000000000000000000000001" +- }, +- "files_id": { +- "$oid": "000000000000000000000001" +- }, +- "n": 0, +- "data": { +- "$hex": "11" +- } +- }, +- { +- "_id": { +- "$oid": "000000000000000000000002" +- }, +- "files_id": { +- "$oid": "000000000000000000000002" +- }, +- "n": 0, +- "data": { +- "$hex": "22" +- } +- }, +- { +- "_id": { +- "$oid": "000000000000000000000003" +- }, +- "files_id": { +- "$oid": "000000000000000000000003" +- }, +- "n": 0, +- "data": { +- "$hex": "33" +- } +- }, +- { +- "_id": { +- "$oid": "000000000000000000000004" +- }, +- "files_id": { +- "$oid": "000000000000000000000004" +- }, +- "n": 0, +- "data": { +- "$hex": "44" +- } +- }, +- { +- "_id": { +- "$oid": "000000000000000000000005" +- }, +- "files_id": { +- "$oid": "000000000000000000000005" +- }, +- "n": 0, +- "data": { +- "$hex": "55" +- } +- } +- ] +- }, +- "tests": [ +- { +- "description": "Download_by_name when revision is 0", +- "act": { +- "operation": "download_by_name", +- "arguments": { +- "filename": "abc", +- "options": { +- "revision": 0 +- } +- } +- }, +- "assert": { +- "result": { +- "$hex": "11" +- } +- } +- }, +- { +- "description": "Download_by_name when revision is 1", +- "act": { +- "operation": "download_by_name", +- "arguments": { +- "filename": "abc", +- "options": { +- "revision": 1 +- } +- } +- }, +- "assert": { +- "result": { +- "$hex": "22" +- } +- } +- }, +- { +- "description": "Download_by_name when revision is -2", +- "act": { +- "operation": "download_by_name", +- "arguments": { +- "filename": "abc", +- "options": { +- "revision": -2 +- } +- } +- }, +- "assert": { +- "result": { +- "$hex": "44" +- } +- } +- }, +- { +- "description": "Download_by_name when revision is -1", +- "act": { +- "operation": "download_by_name", +- "arguments": { +- "filename": "abc", +- "options": { +- "revision": -1 +- } +- } +- }, +- "assert": { +- "result": { +- "$hex": "55" +- } +- } +- }, +- { +- "description": "Download_by_name when files entry does not exist", +- "act": { +- "operation": "download_by_name", +- "arguments": { +- "filename": "xyz" +- } +- }, +- "assert": { +- "error": "FileNotFound" +- } +- }, +- { +- "description": "Download_by_name when revision does not exist", +- "act": { +- "operation": "download_by_name", +- "arguments": { +- "filename": "abc", +- "options": { +- "revision": 999 +- } +- } +- }, +- "assert": { +- "error": "RevisionNotFound" +- } +- } +- ] +-} +--- /dev/null ++++ b/src/libmongoc/tests/json/gridfs/legacy/delete.json +@@ -0,0 +1,291 @@ ++{ ++ "data": { ++ "files": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "length": 0, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "md5": "d41d8cd98f00b204e9800998ecf8427e", ++ "filename": "length-0", ++ "contentType": "application/octet-stream", ++ "aliases": [], ++ "metadata": {} ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000002" ++ }, ++ "length": 0, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "md5": "d41d8cd98f00b204e9800998ecf8427e", ++ "filename": "length-0-with-empty-chunk", ++ "contentType": "application/octet-stream", ++ "aliases": [], ++ "metadata": {} ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000003" ++ }, ++ "length": 2, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "md5": "c700ed4fdb1d27055aa3faa2c2432283", ++ "filename": "length-2", ++ "contentType": "application/octet-stream", ++ "aliases": [], ++ "metadata": {} ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000004" ++ }, ++ "length": 8, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "md5": "dd254cdc958e53abaa67da9f797125f5", ++ "filename": "length-8", ++ "contentType": "application/octet-stream", ++ "aliases": [], ++ "metadata": {} ++ } ++ ], ++ "chunks": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000002" ++ }, ++ "n": 0, ++ "data": { ++ "$hex": "" ++ } ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000002" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000003" ++ }, ++ "n": 0, ++ "data": { ++ "$hex": "1122" ++ } ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000003" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000004" ++ }, ++ "n": 0, ++ "data": { ++ "$hex": "11223344" ++ } ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000004" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000004" ++ }, ++ "n": 1, ++ "data": { ++ "$hex": "55667788" ++ } ++ } ++ ] ++ }, ++ "tests": [ ++ { ++ "description": "Delete when length is 0", ++ "act": { ++ "operation": "delete", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ } ++ }, ++ "assert": { ++ "result": "void", ++ "data": [ ++ { ++ "delete": "expected.files", ++ "deletes": [ ++ { ++ "q": { ++ "_id": { ++ "$oid": "000000000000000000000001" ++ } ++ }, ++ "limit": 1 ++ } ++ ] ++ } ++ ] ++ } ++ }, ++ { ++ "description": "Delete when length is 0 and there is one extra empty chunk", ++ "act": { ++ "operation": "delete", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000002" ++ } ++ } ++ }, ++ "assert": { ++ "result": "void", ++ "data": [ ++ { ++ "delete": "expected.files", ++ "deletes": [ ++ { ++ "q": { ++ "_id": { ++ "$oid": "000000000000000000000002" ++ } ++ }, ++ "limit": 1 ++ } ++ ] ++ }, ++ { ++ "delete": "expected.chunks", ++ "deletes": [ ++ { ++ "q": { ++ "files_id": { ++ "$oid": "000000000000000000000002" ++ } ++ }, ++ "limit": 0 ++ } ++ ] ++ } ++ ] ++ } ++ }, ++ { ++ "description": "Delete when length is 8", ++ "act": { ++ "operation": "delete", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000004" ++ } ++ } ++ }, ++ "assert": { ++ "result": "void", ++ "data": [ ++ { ++ "delete": "expected.files", ++ "deletes": [ ++ { ++ "q": { ++ "_id": { ++ "$oid": "000000000000000000000004" ++ } ++ }, ++ "limit": 1 ++ } ++ ] ++ }, ++ { ++ "delete": "expected.chunks", ++ "deletes": [ ++ { ++ "q": { ++ "files_id": { ++ "$oid": "000000000000000000000004" ++ } ++ }, ++ "limit": 0 ++ } ++ ] ++ } ++ ] ++ } ++ }, ++ { ++ "description": "Delete when files entry does not exist", ++ "act": { ++ "operation": "delete", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000000" ++ } ++ } ++ }, ++ "assert": { ++ "error": "FileNotFound" ++ } ++ }, ++ { ++ "description": "Delete when files entry does not exist and there are orphaned chunks", ++ "arrange": { ++ "data": [ ++ { ++ "delete": "fs.files", ++ "deletes": [ ++ { ++ "q": { ++ "_id": { ++ "$oid": "000000000000000000000004" ++ } ++ }, ++ "limit": 1 ++ } ++ ] ++ } ++ ] ++ }, ++ "act": { ++ "operation": "delete", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000004" ++ } ++ } ++ }, ++ "assert": { ++ "error": "FileNotFound", ++ "data": [ ++ { ++ "delete": "expected.files", ++ "deletes": [ ++ { ++ "q": { ++ "_id": { ++ "$oid": "000000000000000000000004" ++ } ++ }, ++ "limit": 1 ++ } ++ ] ++ } ++ ] ++ } ++ } ++ ] ++} +--- /dev/null ++++ b/src/libmongoc/tests/json/gridfs/legacy/download.json +@@ -0,0 +1,467 @@ ++{ ++ "data": { ++ "files": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "length": 0, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "md5": "d41d8cd98f00b204e9800998ecf8427e", ++ "filename": "length-0", ++ "contentType": "application/octet-stream", ++ "aliases": [], ++ "metadata": {} ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000002" ++ }, ++ "length": 0, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "md5": "d41d8cd98f00b204e9800998ecf8427e", ++ "filename": "length-0-with-empty-chunk", ++ "contentType": "application/octet-stream", ++ "aliases": [], ++ "metadata": {} ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000003" ++ }, ++ "length": 2, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "md5": "c700ed4fdb1d27055aa3faa2c2432283", ++ "filename": "length-2", ++ "contentType": "application/octet-stream", ++ "aliases": [], ++ "metadata": {} ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000004" ++ }, ++ "length": 8, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "md5": "dd254cdc958e53abaa67da9f797125f5", ++ "filename": "length-8", ++ "contentType": "application/octet-stream", ++ "aliases": [], ++ "metadata": {} ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000005" ++ }, ++ "length": 10, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "md5": "57d83cd477bfb1ccd975ab33d827a92b", ++ "filename": "length-10", ++ "contentType": "application/octet-stream", ++ "aliases": [], ++ "metadata": {} ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000006" ++ }, ++ "length": 2, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "md5": "c700ed4fdb1d27055aa3faa2c2432283", ++ "contentType": "application/octet-stream", ++ "aliases": [], ++ "metadata": {} ++ } ++ ], ++ "chunks": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000002" ++ }, ++ "n": 0, ++ "data": { ++ "$hex": "" ++ } ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000002" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000003" ++ }, ++ "n": 0, ++ "data": { ++ "$hex": "1122" ++ } ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000003" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000004" ++ }, ++ "n": 0, ++ "data": { ++ "$hex": "11223344" ++ } ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000004" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000004" ++ }, ++ "n": 1, ++ "data": { ++ "$hex": "55667788" ++ } ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000005" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000005" ++ }, ++ "n": 0, ++ "data": { ++ "$hex": "11223344" ++ } ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000006" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000005" ++ }, ++ "n": 1, ++ "data": { ++ "$hex": "55667788" ++ } ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000007" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000005" ++ }, ++ "n": 2, ++ "data": { ++ "$hex": "99aa" ++ } ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000008" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000006" ++ }, ++ "n": 0, ++ "data": { ++ "$hex": "1122" ++ } ++ } ++ ] ++ }, ++ "tests": [ ++ { ++ "description": "Download when length is zero", ++ "act": { ++ "operation": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "options": {} ++ } ++ }, ++ "assert": { ++ "result": { ++ "$hex": "" ++ } ++ } ++ }, ++ { ++ "description": "Download when length is zero and there is one empty chunk", ++ "act": { ++ "operation": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000002" ++ }, ++ "options": {} ++ } ++ }, ++ "assert": { ++ "result": { ++ "$hex": "" ++ } ++ } ++ }, ++ { ++ "description": "Download when there is one chunk", ++ "act": { ++ "operation": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000003" ++ }, ++ "options": {} ++ } ++ }, ++ "assert": { ++ "result": { ++ "$hex": "1122" ++ } ++ } ++ }, ++ { ++ "description": "Download when there are two chunks", ++ "act": { ++ "operation": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000004" ++ }, ++ "options": {} ++ } ++ }, ++ "assert": { ++ "result": { ++ "$hex": "1122334455667788" ++ } ++ } ++ }, ++ { ++ "description": "Download when there are three chunks", ++ "act": { ++ "operation": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000005" ++ }, ++ "options": {} ++ } ++ }, ++ "assert": { ++ "result": { ++ "$hex": "112233445566778899aa" ++ } ++ } ++ }, ++ { ++ "description": "Download when files entry does not exist", ++ "act": { ++ "operation": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000000" ++ }, ++ "options": {} ++ } ++ }, ++ "assert": { ++ "error": "FileNotFound" ++ } ++ }, ++ { ++ "description": "Download when an intermediate chunk is missing", ++ "arrange": { ++ "data": [ ++ { ++ "delete": "fs.chunks", ++ "deletes": [ ++ { ++ "q": { ++ "files_id": { ++ "$oid": "000000000000000000000005" ++ }, ++ "n": 1 ++ }, ++ "limit": 1 ++ } ++ ] ++ } ++ ] ++ }, ++ "act": { ++ "operation": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000005" ++ } ++ } ++ }, ++ "assert": { ++ "error": "ChunkIsMissing" ++ } ++ }, ++ { ++ "description": "Download when final chunk is missing", ++ "arrange": { ++ "data": [ ++ { ++ "delete": "fs.chunks", ++ "deletes": [ ++ { ++ "q": { ++ "files_id": { ++ "$oid": "000000000000000000000005" ++ }, ++ "n": 1 ++ }, ++ "limit": 1 ++ } ++ ] ++ } ++ ] ++ }, ++ "act": { ++ "operation": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000005" ++ } ++ } ++ }, ++ "assert": { ++ "error": "ChunkIsMissing" ++ } ++ }, ++ { ++ "description": "Download when an intermediate chunk is the wrong size", ++ "arrange": { ++ "data": [ ++ { ++ "update": "fs.chunks", ++ "updates": [ ++ { ++ "q": { ++ "files_id": { ++ "$oid": "000000000000000000000005" ++ }, ++ "n": 1 ++ }, ++ "u": { ++ "$set": { ++ "data": { ++ "$hex": "556677" ++ } ++ } ++ } ++ }, ++ { ++ "q": { ++ "files_id": { ++ "$oid": "000000000000000000000005" ++ }, ++ "n": 2 ++ }, ++ "u": { ++ "$set": { ++ "data": { ++ "$hex": "8899aa" ++ } ++ } ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ "act": { ++ "operation": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000005" ++ } ++ } ++ }, ++ "assert": { ++ "error": "ChunkIsWrongSize" ++ } ++ }, ++ { ++ "description": "Download when final chunk is the wrong size", ++ "arrange": { ++ "data": [ ++ { ++ "update": "fs.chunks", ++ "updates": [ ++ { ++ "q": { ++ "files_id": { ++ "$oid": "000000000000000000000005" ++ }, ++ "n": 2 ++ }, ++ "u": { ++ "$set": { ++ "data": { ++ "$hex": "99" ++ } ++ } ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ "act": { ++ "operation": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000005" ++ } ++ } ++ }, ++ "assert": { ++ "error": "ChunkIsWrongSize" ++ } ++ }, ++ { ++ "description": "Download legacy file with no name", ++ "act": { ++ "operation": "download", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000006" ++ }, ++ "options": {} ++ } ++ }, ++ "assert": { ++ "result": { ++ "$hex": "1122" ++ } ++ } ++ } ++ ] ++} +--- /dev/null ++++ b/src/libmongoc/tests/json/gridfs/legacy/download_by_name.json +@@ -0,0 +1,240 @@ ++{ ++ "data": { ++ "files": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "length": 1, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "md5": "47ed733b8d10be225eceba344d533586", ++ "filename": "abc", ++ "contentType": "application/octet-stream", ++ "aliases": [], ++ "metadata": {} ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000002" ++ }, ++ "length": 1, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-02T00:00:00.000Z" ++ }, ++ "md5": "b15835f133ff2e27c7cb28117bfae8f4", ++ "filename": "abc", ++ "contentType": "application/octet-stream", ++ "aliases": [], ++ "metadata": {} ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000003" ++ }, ++ "length": 1, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-03T00:00:00.000Z" ++ }, ++ "md5": "eccbc87e4b5ce2fe28308fd9f2a7baf3", ++ "filename": "abc", ++ "contentType": "application/octet-stream", ++ "aliases": [], ++ "metadata": {} ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000004" ++ }, ++ "length": 1, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-04T00:00:00.000Z" ++ }, ++ "md5": "f623e75af30e62bbd73d6df5b50bb7b5", ++ "filename": "abc", ++ "contentType": "application/octet-stream", ++ "aliases": [], ++ "metadata": {} ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000005" ++ }, ++ "length": 1, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-05T00:00:00.000Z" ++ }, ++ "md5": "4c614360da93c0a041b22e537de151eb", ++ "filename": "abc", ++ "contentType": "application/octet-stream", ++ "aliases": [], ++ "metadata": {} ++ } ++ ], ++ "chunks": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "n": 0, ++ "data": { ++ "$hex": "11" ++ } ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000002" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000002" ++ }, ++ "n": 0, ++ "data": { ++ "$hex": "22" ++ } ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000003" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000003" ++ }, ++ "n": 0, ++ "data": { ++ "$hex": "33" ++ } ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000004" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000004" ++ }, ++ "n": 0, ++ "data": { ++ "$hex": "44" ++ } ++ }, ++ { ++ "_id": { ++ "$oid": "000000000000000000000005" ++ }, ++ "files_id": { ++ "$oid": "000000000000000000000005" ++ }, ++ "n": 0, ++ "data": { ++ "$hex": "55" ++ } ++ } ++ ] ++ }, ++ "tests": [ ++ { ++ "description": "Download_by_name when revision is 0", ++ "act": { ++ "operation": "download_by_name", ++ "arguments": { ++ "filename": "abc", ++ "options": { ++ "revision": 0 ++ } ++ } ++ }, ++ "assert": { ++ "result": { ++ "$hex": "11" ++ } ++ } ++ }, ++ { ++ "description": "Download_by_name when revision is 1", ++ "act": { ++ "operation": "download_by_name", ++ "arguments": { ++ "filename": "abc", ++ "options": { ++ "revision": 1 ++ } ++ } ++ }, ++ "assert": { ++ "result": { ++ "$hex": "22" ++ } ++ } ++ }, ++ { ++ "description": "Download_by_name when revision is -2", ++ "act": { ++ "operation": "download_by_name", ++ "arguments": { ++ "filename": "abc", ++ "options": { ++ "revision": -2 ++ } ++ } ++ }, ++ "assert": { ++ "result": { ++ "$hex": "44" ++ } ++ } ++ }, ++ { ++ "description": "Download_by_name when revision is -1", ++ "act": { ++ "operation": "download_by_name", ++ "arguments": { ++ "filename": "abc", ++ "options": { ++ "revision": -1 ++ } ++ } ++ }, ++ "assert": { ++ "result": { ++ "$hex": "55" ++ } ++ } ++ }, ++ { ++ "description": "Download_by_name when files entry does not exist", ++ "act": { ++ "operation": "download_by_name", ++ "arguments": { ++ "filename": "xyz" ++ } ++ }, ++ "assert": { ++ "error": "FileNotFound" ++ } ++ }, ++ { ++ "description": "Download_by_name when revision does not exist", ++ "act": { ++ "operation": "download_by_name", ++ "arguments": { ++ "filename": "abc", ++ "options": { ++ "revision": 999 ++ } ++ } ++ }, ++ "assert": { ++ "error": "RevisionNotFound" ++ } ++ } ++ ] ++} +--- /dev/null ++++ b/src/libmongoc/tests/json/gridfs/legacy/upload.json +@@ -0,0 +1,466 @@ ++{ ++ "data": { ++ "files": [], ++ "chunks": [] ++ }, ++ "tests": [ ++ { ++ "description": "Upload when length is 0", ++ "act": { ++ "operation": "upload", ++ "arguments": { ++ "filename": "filename", ++ "source": { ++ "$hex": "" ++ }, ++ "options": { ++ "chunkSizeBytes": 4 ++ } ++ } ++ }, ++ "assert": { ++ "result": "&result", ++ "data": [ ++ { ++ "insert": "expected.files", ++ "documents": [ ++ { ++ "_id": "*result", ++ "length": 0, ++ "chunkSize": 4, ++ "uploadDate": "*actual", ++ "md5": "d41d8cd98f00b204e9800998ecf8427e", ++ "filename": "filename" ++ } ++ ] ++ } ++ ] ++ } ++ }, ++ { ++ "description": "Upload when length is 1", ++ "act": { ++ "operation": "upload", ++ "arguments": { ++ "filename": "filename", ++ "source": { ++ "$hex": "11" ++ }, ++ "options": { ++ "chunkSizeBytes": 4 ++ } ++ } ++ }, ++ "assert": { ++ "result": "&result", ++ "data": [ ++ { ++ "insert": "expected.files", ++ "documents": [ ++ { ++ "_id": "*result", ++ "length": 1, ++ "chunkSize": 4, ++ "uploadDate": "*actual", ++ "md5": "47ed733b8d10be225eceba344d533586", ++ "filename": "filename" ++ } ++ ] ++ }, ++ { ++ "insert": "expected.chunks", ++ "documents": [ ++ { ++ "_id": "*actual", ++ "files_id": "*result", ++ "n": 0, ++ "data": { ++ "$hex": "11" ++ } ++ } ++ ] ++ } ++ ] ++ } ++ }, ++ { ++ "description": "Upload when length is 3", ++ "act": { ++ "operation": "upload", ++ "arguments": { ++ "filename": "filename", ++ "source": { ++ "$hex": "112233" ++ }, ++ "options": { ++ "chunkSizeBytes": 4 ++ } ++ } ++ }, ++ "assert": { ++ "result": "&result", ++ "data": [ ++ { ++ "insert": "expected.files", ++ "documents": [ ++ { ++ "_id": "*result", ++ "length": 3, ++ "chunkSize": 4, ++ "uploadDate": "*actual", ++ "md5": "bafae3a174ab91fc70db7a6aa50f4f52", ++ "filename": "filename" ++ } ++ ] ++ }, ++ { ++ "insert": "expected.chunks", ++ "documents": [ ++ { ++ "_id": "*actual", ++ "files_id": "*result", ++ "n": 0, ++ "data": { ++ "$hex": "112233" ++ } ++ } ++ ] ++ } ++ ] ++ } ++ }, ++ { ++ "description": "Upload when length is 4", ++ "act": { ++ "operation": "upload", ++ "arguments": { ++ "filename": "filename", ++ "source": { ++ "$hex": "11223344" ++ }, ++ "options": { ++ "chunkSizeBytes": 4 ++ } ++ } ++ }, ++ "assert": { ++ "result": "&result", ++ "data": [ ++ { ++ "insert": "expected.files", ++ "documents": [ ++ { ++ "_id": "*result", ++ "length": 4, ++ "chunkSize": 4, ++ "uploadDate": "*actual", ++ "md5": "7e7c77cff5705d1f7574a25ef6662117", ++ "filename": "filename" ++ } ++ ] ++ }, ++ { ++ "insert": "expected.chunks", ++ "documents": [ ++ { ++ "_id": "*actual", ++ "files_id": "*result", ++ "n": 0, ++ "data": { ++ "$hex": "11223344" ++ } ++ } ++ ] ++ } ++ ] ++ } ++ }, ++ { ++ "description": "Upload when length is 5", ++ "act": { ++ "operation": "upload", ++ "arguments": { ++ "filename": "filename", ++ "source": { ++ "$hex": "1122334455" ++ }, ++ "options": { ++ "chunkSizeBytes": 4 ++ } ++ } ++ }, ++ "assert": { ++ "result": "&result", ++ "data": [ ++ { ++ "insert": "expected.files", ++ "documents": [ ++ { ++ "_id": "*result", ++ "length": 5, ++ "chunkSize": 4, ++ "uploadDate": "*actual", ++ "md5": "283d4fea5dded59cf837d3047328f5af", ++ "filename": "filename" ++ } ++ ] ++ }, ++ { ++ "insert": "expected.chunks", ++ "documents": [ ++ { ++ "_id": "*actual", ++ "files_id": "*result", ++ "n": 0, ++ "data": { ++ "$hex": "11223344" ++ } ++ }, ++ { ++ "_id": "*actual", ++ "files_id": "*result", ++ "n": 1, ++ "data": { ++ "$hex": "55" ++ } ++ } ++ ] ++ } ++ ] ++ } ++ }, ++ { ++ "description": "Upload when length is 8", ++ "act": { ++ "operation": "upload", ++ "arguments": { ++ "filename": "filename", ++ "source": { ++ "$hex": "1122334455667788" ++ }, ++ "options": { ++ "chunkSizeBytes": 4 ++ } ++ } ++ }, ++ "assert": { ++ "result": "&result", ++ "data": [ ++ { ++ "insert": "expected.files", ++ "documents": [ ++ { ++ "_id": "*result", ++ "length": 8, ++ "chunkSize": 4, ++ "uploadDate": "*actual", ++ "md5": "dd254cdc958e53abaa67da9f797125f5", ++ "filename": "filename" ++ } ++ ] ++ }, ++ { ++ "insert": "expected.chunks", ++ "documents": [ ++ { ++ "_id": "*actual", ++ "files_id": "*result", ++ "n": 0, ++ "data": { ++ "$hex": "11223344" ++ } ++ }, ++ { ++ "_id": "*actual", ++ "files_id": "*result", ++ "n": 1, ++ "data": { ++ "$hex": "55667788" ++ } ++ } ++ ] ++ } ++ ] ++ } ++ }, ++ { ++ "description": "Upload when contentType is provided", ++ "act": { ++ "operation": "upload", ++ "arguments": { ++ "filename": "filename", ++ "source": { ++ "$hex": "11" ++ }, ++ "options": { ++ "chunkSizeBytes": 4, ++ "contentType": "image/jpeg" ++ } ++ } ++ }, ++ "assert": { ++ "result": "&result", ++ "data": [ ++ { ++ "insert": "expected.files", ++ "documents": [ ++ { ++ "_id": "*result", ++ "length": 1, ++ "chunkSize": 4, ++ "uploadDate": "*actual", ++ "md5": "47ed733b8d10be225eceba344d533586", ++ "filename": "filename", ++ "contentType": "image/jpeg" ++ } ++ ] ++ }, ++ { ++ "insert": "expected.chunks", ++ "documents": [ ++ { ++ "_id": "*actual", ++ "files_id": "*result", ++ "n": 0, ++ "data": { ++ "$hex": "11" ++ } ++ } ++ ] ++ } ++ ] ++ } ++ }, ++ { ++ "description": "Upload when metadata is provided", ++ "act": { ++ "operation": "upload", ++ "arguments": { ++ "filename": "filename", ++ "source": { ++ "$hex": "11" ++ }, ++ "options": { ++ "chunkSizeBytes": 4, ++ "metadata": { ++ "x": 1 ++ } ++ } ++ } ++ }, ++ "assert": { ++ "result": "&result", ++ "data": [ ++ { ++ "insert": "expected.files", ++ "documents": [ ++ { ++ "_id": "*result", ++ "length": 1, ++ "chunkSize": 4, ++ "uploadDate": "*actual", ++ "md5": "47ed733b8d10be225eceba344d533586", ++ "filename": "filename", ++ "metadata": { ++ "x": 1 ++ } ++ } ++ ] ++ }, ++ { ++ "insert": "expected.chunks", ++ "documents": [ ++ { ++ "_id": "*actual", ++ "files_id": "*result", ++ "n": 0, ++ "data": { ++ "$hex": "11" ++ } ++ } ++ ] ++ } ++ ] ++ } ++ }, ++ { ++ "description": "Upload when length is 0 sans MD5", ++ "act": { ++ "operation": "upload", ++ "arguments": { ++ "filename": "filename", ++ "source": { ++ "$hex": "" ++ }, ++ "options": { ++ "chunkSizeBytes": 4, ++ "disableMD5": true ++ } ++ } ++ }, ++ "assert": { ++ "result": "&result", ++ "data": [ ++ { ++ "insert": "expected.files", ++ "documents": [ ++ { ++ "_id": "*result", ++ "length": 0, ++ "chunkSize": 4, ++ "uploadDate": "*actual", ++ "filename": "filename" ++ } ++ ] ++ } ++ ] ++ } ++ }, ++ { ++ "description": "Upload when length is 1 sans MD5", ++ "act": { ++ "operation": "upload", ++ "arguments": { ++ "filename": "filename", ++ "source": { ++ "$hex": "11" ++ }, ++ "options": { ++ "chunkSizeBytes": 4, ++ "disableMD5": true ++ } ++ } ++ }, ++ "assert": { ++ "result": "&result", ++ "data": [ ++ { ++ "insert": "expected.files", ++ "documents": [ ++ { ++ "_id": "*result", ++ "length": 1, ++ "chunkSize": 4, ++ "uploadDate": "*actual", ++ "filename": "filename" ++ } ++ ] ++ }, ++ { ++ "insert": "expected.chunks", ++ "documents": [ ++ { ++ "_id": "*actual", ++ "files_id": "*result", ++ "n": 0, ++ "data": { ++ "$hex": "11" ++ } ++ } ++ ] ++ } ++ ] ++ } ++ } ++ ] ++} +--- a/src/libmongoc/tests/json/gridfs/upload.json ++++ /dev/null +@@ -1,466 +0,0 @@ +-{ +- "data": { +- "files": [], +- "chunks": [] +- }, +- "tests": [ +- { +- "description": "Upload when length is 0", +- "act": { +- "operation": "upload", +- "arguments": { +- "filename": "filename", +- "source": { +- "$hex": "" +- }, +- "options": { +- "chunkSizeBytes": 4 +- } +- } +- }, +- "assert": { +- "result": "&result", +- "data": [ +- { +- "insert": "expected.files", +- "documents": [ +- { +- "_id": "*result", +- "length": 0, +- "chunkSize": 4, +- "uploadDate": "*actual", +- "md5": "d41d8cd98f00b204e9800998ecf8427e", +- "filename": "filename" +- } +- ] +- } +- ] +- } +- }, +- { +- "description": "Upload when length is 1", +- "act": { +- "operation": "upload", +- "arguments": { +- "filename": "filename", +- "source": { +- "$hex": "11" +- }, +- "options": { +- "chunkSizeBytes": 4 +- } +- } +- }, +- "assert": { +- "result": "&result", +- "data": [ +- { +- "insert": "expected.files", +- "documents": [ +- { +- "_id": "*result", +- "length": 1, +- "chunkSize": 4, +- "uploadDate": "*actual", +- "md5": "47ed733b8d10be225eceba344d533586", +- "filename": "filename" +- } +- ] +- }, +- { +- "insert": "expected.chunks", +- "documents": [ +- { +- "_id": "*actual", +- "files_id": "*result", +- "n": 0, +- "data": { +- "$hex": "11" +- } +- } +- ] +- } +- ] +- } +- }, +- { +- "description": "Upload when length is 3", +- "act": { +- "operation": "upload", +- "arguments": { +- "filename": "filename", +- "source": { +- "$hex": "112233" +- }, +- "options": { +- "chunkSizeBytes": 4 +- } +- } +- }, +- "assert": { +- "result": "&result", +- "data": [ +- { +- "insert": "expected.files", +- "documents": [ +- { +- "_id": "*result", +- "length": 3, +- "chunkSize": 4, +- "uploadDate": "*actual", +- "md5": "bafae3a174ab91fc70db7a6aa50f4f52", +- "filename": "filename" +- } +- ] +- }, +- { +- "insert": "expected.chunks", +- "documents": [ +- { +- "_id": "*actual", +- "files_id": "*result", +- "n": 0, +- "data": { +- "$hex": "112233" +- } +- } +- ] +- } +- ] +- } +- }, +- { +- "description": "Upload when length is 4", +- "act": { +- "operation": "upload", +- "arguments": { +- "filename": "filename", +- "source": { +- "$hex": "11223344" +- }, +- "options": { +- "chunkSizeBytes": 4 +- } +- } +- }, +- "assert": { +- "result": "&result", +- "data": [ +- { +- "insert": "expected.files", +- "documents": [ +- { +- "_id": "*result", +- "length": 4, +- "chunkSize": 4, +- "uploadDate": "*actual", +- "md5": "7e7c77cff5705d1f7574a25ef6662117", +- "filename": "filename" +- } +- ] +- }, +- { +- "insert": "expected.chunks", +- "documents": [ +- { +- "_id": "*actual", +- "files_id": "*result", +- "n": 0, +- "data": { +- "$hex": "11223344" +- } +- } +- ] +- } +- ] +- } +- }, +- { +- "description": "Upload when length is 5", +- "act": { +- "operation": "upload", +- "arguments": { +- "filename": "filename", +- "source": { +- "$hex": "1122334455" +- }, +- "options": { +- "chunkSizeBytes": 4 +- } +- } +- }, +- "assert": { +- "result": "&result", +- "data": [ +- { +- "insert": "expected.files", +- "documents": [ +- { +- "_id": "*result", +- "length": 5, +- "chunkSize": 4, +- "uploadDate": "*actual", +- "md5": "283d4fea5dded59cf837d3047328f5af", +- "filename": "filename" +- } +- ] +- }, +- { +- "insert": "expected.chunks", +- "documents": [ +- { +- "_id": "*actual", +- "files_id": "*result", +- "n": 0, +- "data": { +- "$hex": "11223344" +- } +- }, +- { +- "_id": "*actual", +- "files_id": "*result", +- "n": 1, +- "data": { +- "$hex": "55" +- } +- } +- ] +- } +- ] +- } +- }, +- { +- "description": "Upload when length is 8", +- "act": { +- "operation": "upload", +- "arguments": { +- "filename": "filename", +- "source": { +- "$hex": "1122334455667788" +- }, +- "options": { +- "chunkSizeBytes": 4 +- } +- } +- }, +- "assert": { +- "result": "&result", +- "data": [ +- { +- "insert": "expected.files", +- "documents": [ +- { +- "_id": "*result", +- "length": 8, +- "chunkSize": 4, +- "uploadDate": "*actual", +- "md5": "dd254cdc958e53abaa67da9f797125f5", +- "filename": "filename" +- } +- ] +- }, +- { +- "insert": "expected.chunks", +- "documents": [ +- { +- "_id": "*actual", +- "files_id": "*result", +- "n": 0, +- "data": { +- "$hex": "11223344" +- } +- }, +- { +- "_id": "*actual", +- "files_id": "*result", +- "n": 1, +- "data": { +- "$hex": "55667788" +- } +- } +- ] +- } +- ] +- } +- }, +- { +- "description": "Upload when contentType is provided", +- "act": { +- "operation": "upload", +- "arguments": { +- "filename": "filename", +- "source": { +- "$hex": "11" +- }, +- "options": { +- "chunkSizeBytes": 4, +- "contentType": "image/jpeg" +- } +- } +- }, +- "assert": { +- "result": "&result", +- "data": [ +- { +- "insert": "expected.files", +- "documents": [ +- { +- "_id": "*result", +- "length": 1, +- "chunkSize": 4, +- "uploadDate": "*actual", +- "md5": "47ed733b8d10be225eceba344d533586", +- "filename": "filename", +- "contentType": "image/jpeg" +- } +- ] +- }, +- { +- "insert": "expected.chunks", +- "documents": [ +- { +- "_id": "*actual", +- "files_id": "*result", +- "n": 0, +- "data": { +- "$hex": "11" +- } +- } +- ] +- } +- ] +- } +- }, +- { +- "description": "Upload when metadata is provided", +- "act": { +- "operation": "upload", +- "arguments": { +- "filename": "filename", +- "source": { +- "$hex": "11" +- }, +- "options": { +- "chunkSizeBytes": 4, +- "metadata": { +- "x": 1 +- } +- } +- } +- }, +- "assert": { +- "result": "&result", +- "data": [ +- { +- "insert": "expected.files", +- "documents": [ +- { +- "_id": "*result", +- "length": 1, +- "chunkSize": 4, +- "uploadDate": "*actual", +- "md5": "47ed733b8d10be225eceba344d533586", +- "filename": "filename", +- "metadata": { +- "x": 1 +- } +- } +- ] +- }, +- { +- "insert": "expected.chunks", +- "documents": [ +- { +- "_id": "*actual", +- "files_id": "*result", +- "n": 0, +- "data": { +- "$hex": "11" +- } +- } +- ] +- } +- ] +- } +- }, +- { +- "description": "Upload when length is 0 sans MD5", +- "act": { +- "operation": "upload", +- "arguments": { +- "filename": "filename", +- "source": { +- "$hex": "" +- }, +- "options": { +- "chunkSizeBytes": 4, +- "disableMD5": true +- } +- } +- }, +- "assert": { +- "result": "&result", +- "data": [ +- { +- "insert": "expected.files", +- "documents": [ +- { +- "_id": "*result", +- "length": 0, +- "chunkSize": 4, +- "uploadDate": "*actual", +- "filename": "filename" +- } +- ] +- } +- ] +- } +- }, +- { +- "description": "Upload when length is 1 sans MD5", +- "act": { +- "operation": "upload", +- "arguments": { +- "filename": "filename", +- "source": { +- "$hex": "11" +- }, +- "options": { +- "chunkSizeBytes": 4, +- "disableMD5": true +- } +- } +- }, +- "assert": { +- "result": "&result", +- "data": [ +- { +- "insert": "expected.files", +- "documents": [ +- { +- "_id": "*result", +- "length": 1, +- "chunkSize": 4, +- "uploadDate": "*actual", +- "filename": "filename" +- } +- ] +- }, +- { +- "insert": "expected.chunks", +- "documents": [ +- { +- "_id": "*actual", +- "files_id": "*result", +- "n": 0, +- "data": { +- "$hex": "11" +- } +- } +- ] +- } +- ] +- } +- } +- ] +-} diff -Nru mongo-c-driver-1.30.4/debian/patches/0016_CVE-2026-93394.patch mongo-c-driver-1.30.4/debian/patches/0016_CVE-2026-93394.patch --- mongo-c-driver-1.30.4/debian/patches/0016_CVE-2026-93394.patch 1970-01-01 00:00:00.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/patches/0016_CVE-2026-93394.patch 2026-11-03 21:12:33.000000000 +0000 @@ -0,0 +1,82 @@ +From 0b9bbbff0b949dcddb97e518e7fdb5950e187be3 Mon Sep 17 00:00:00 2001 +From: Kevin Albertson +Date: Mon, 14 Sep 2026 14:25:18 -0400 +Subject: [PATCH] CDRIVER-6315 fix missing `goto FAIL` along error handling + path (#2308) (#2414) + +(cherry picked from commit 6b27da3e0384170ac0efc75321556bd37a2ae03f) + +Co-authored-by: Connor MacDonald +--- + src/libmongoc/src/mongoc/mongoc-scram.c | 1 + + src/libmongoc/tests/test-mongoc-scram.c | 35 +++++++++++++++++++++++++ + 2 files changed, 36 insertions(+) + +diff --git a/src/libmongoc/src/mongoc/mongoc-scram.c b/src/libmongoc/src/mongoc/mongoc-scram.c +index ac6a3a9cb0..58f787c65f 100644 +--- a/src/libmongoc/src/mongoc/mongoc-scram.c ++++ b/src/libmongoc/src/mongoc/mongoc-scram.c +@@ -670,6 +670,7 @@ _mongoc_scram_step2 (mongoc_scram_t *scram, + MONGOC_ERROR_SCRAM, + MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, + "SCRAM Failure: client nonce not repeated in sasl step 2"); ++ goto FAIL; + } + + *outbuflen = 0; +diff --git a/src/libmongoc/tests/test-mongoc-scram.c b/src/libmongoc/tests/test-mongoc-scram.c +index aad5da07a4..ca36baee3d 100644 +--- a/src/libmongoc/tests/test-mongoc-scram.c ++++ b/src/libmongoc/tests/test-mongoc-scram.c +@@ -88,6 +88,40 @@ test_mongoc_scram_iteration_count (void) + test_iteration_count (10000, true); + } + ++/* Regression test for CDRIVER-6315. */ ++static void ++test_mongoc_scram_step_nonce_mismatch (void) ++{ ++ mongoc_scram_t scram; ++ uint8_t buf[4096] = {0}; ++ uint32_t buflen = 0; ++ bson_error_t error; ++ const char *client_nonce = "YWJjZA=="; ++ /* server-first message whose r= field does not begin with the client's nonce */ ++ const char *server_response = "r=ZZZZZZZZserver-nonce,s=r6+P1iLmSJvhrRyuFi6Wsg==,i=4096"; ++ bool success; ++ ++ _mongoc_scram_init (&scram, MONGOC_CRYPTO_ALGORITHM_SHA_1); ++ _mongoc_scram_set_pass (&scram, "password"); ++ bson_strncpy (scram.encoded_nonce, client_nonce, sizeof (scram.encoded_nonce)); ++ scram.encoded_nonce_len = (int32_t) strlen (client_nonce); ++ scram.auth_message = bson_malloc0 (4096); ++ scram.auth_messagemax = 4096; ++ memcpy (buf, server_response, strlen (server_response) + 1); ++ buflen = (int32_t) strlen (server_response); ++ scram.step = 1; ++ ++ success = _mongoc_scram_step (&scram, buf, buflen, buf, sizeof buf, &buflen, &error); ++ ++ BSON_ASSERT (!success); ++ ASSERT_ERROR_CONTAINS (error, ++ MONGOC_ERROR_SCRAM, ++ MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, ++ "SCRAM Failure: client nonce not repeated in sasl step 2"); ++ ++ _mongoc_scram_destroy (&scram); ++} ++ + static void + test_mongoc_scram_sasl_prep (void) + { +@@ -744,6 +778,7 @@ test_scram_install (TestSuite *suite) + TestSuite_Add (suite, "/scram/username_not_set", test_mongoc_scram_step_username_not_set); + TestSuite_Add (suite, "/scram/sasl_prep", test_mongoc_scram_sasl_prep); + TestSuite_Add (suite, "/scram/iteration_count", test_mongoc_scram_iteration_count); ++ TestSuite_Add (suite, "/scram/nonce_mismatch", test_mongoc_scram_step_nonce_mismatch); + TestSuite_Add (suite, "/scram/utf8_char_length", test_mongoc_utf8_char_length); + TestSuite_Add (suite, "/scram/utf8_string_length", test_mongoc_utf8_string_length); + TestSuite_Add (suite, "/scram/utf8_to_unicode", test_mongoc_utf8_to_unicode); +-- +2.39.5 + diff -Nru mongo-c-driver-1.30.4/debian/patches/0017_CVE-2026-96746.patch mongo-c-driver-1.30.4/debian/patches/0017_CVE-2026-96746.patch --- mongo-c-driver-1.30.4/debian/patches/0017_CVE-2026-96746.patch 1970-01-01 00:00:00.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/patches/0017_CVE-2026-96746.patch 2026-11-03 21:12:33.000000000 +0000 @@ -0,0 +1,172 @@ +From 59bcc756ad8f04af74b84b88ab747adfd2647b5b Mon Sep 17 00:00:00 2001 +From: Kevin Albertson +Date: Wed, 23 Sep 2026 16:34:52 -0400 +Subject: [PATCH] CDRIVER-6404 check and resize poll set + +--- + src/libmongoc/src/mongoc/mongoc-async.c | 9 ++ + src/libmongoc/tests/test-mongoc-async.c | 123 ++++++++++++++++++++++++ + 2 files changed, 132 insertions(+) + +diff --git a/src/libmongoc/src/mongoc/mongoc-async.c b/src/libmongoc/src/mongoc/mongoc-async.c +index 8804f4c682..f6627a52d0 100644 +--- a/src/libmongoc/src/mongoc/mongoc-async.c ++++ b/src/libmongoc/src/mongoc/mongoc-async.c +@@ -102,6 +102,15 @@ mongoc_async_run (mongoc_async_t *async) + } + + if (acmd->stream) { ++ // Resize the set of polled streams if needed to match the number of async commands. ++ // In rare cases, `mongoc_async_cmd_run` may increase the number of async commands due to reconnecting. ++ if ((size_t) nstreams == poll_size) { ++ ++poll_size; ++ poller = (mongoc_stream_poll_t *) bson_realloc (poller, sizeof (*poller) * poll_size); ++ acmds_polled = (mongoc_async_cmd_t **) bson_realloc (acmds_polled, sizeof (*acmds_polled) * poll_size); ++ } ++ BSON_ASSERT ((size_t) nstreams < poll_size); ++ + acmds_polled[nstreams] = acmd; + poller[nstreams].stream = acmd->stream; + poller[nstreams].events = acmd->events; +diff --git a/src/libmongoc/tests/test-mongoc-async.c b/src/libmongoc/tests/test-mongoc-async.c +index 3ea31152cb..643f5f53eb 100644 +--- a/src/libmongoc/tests/test-mongoc-async.c ++++ b/src/libmongoc/tests/test-mongoc-async.c +@@ -366,6 +366,128 @@ test_hello_delay (void) + mock_server_destroy (server); + } + ++// Test that `mongoc_async_run` can handle adding async commands mid-loop. ++// This is a regression test for CDRIVER-6404. ++// ++// Two commands are needed to start. `DL_FOREACH_SAFE` captures the next pointer before running a command, so a command ++// appended while iterating the last is processed in the next loop pass. But a command appended while iterating the ++// first (of two) is processed in the same pass. ++#define GROW_NSTREAMS 3 ++ ++typedef struct _grow_ctx_t { ++ mongoc_async_t *async; ++ bson_t *cmd; ++ mongoc_stream_t *streams[GROW_NSTREAMS]; ++ bool appended; ++ int nfinished; ++} grow_ctx_t; ++ ++static void ++test_grow_callback (mongoc_async_cmd_t *acmd, ++ mongoc_async_cmd_result_t result, ++ const bson_t *bson, ++ int64_t duration_usec) ++{ ++ BSON_UNUSED (bson); ++ BSON_UNUSED (duration_usec); ++ ++ if (result == MONGOC_ASYNC_CMD_CONNECTED) { ++ return; ++ } ++ ++ ((grow_ctx_t *) acmd->data)->nfinished++; ++} ++ ++// Reports a successful connect and appends a new command. ++static mongoc_stream_t * ++test_grow_initiator (mongoc_async_cmd_t *acmd) ++{ ++ grow_ctx_t *ctx = (grow_ctx_t *) acmd->data; ++ ++ BSON_ASSERT (!ctx->appended); ++ ctx->appended = true; ++ ++ mongoc_async_cmd_new (ctx->async, ++ ctx->streams[2], ++ false, /* is setup done. */ ++ NULL, /* dns result. */ ++ NULL, /* initiator. */ ++ 0, /* no delay. */ ++ NULL, /* setup function. */ ++ NULL, /* setup ctx. */ ++ "admin", ++ ctx->cmd, ++ MONGOC_OP_CODE_QUERY, ++ &test_grow_callback, ++ ctx, ++ TIMEOUT); ++ ++ return ctx->streams[0]; ++} ++ ++static void ++test_async_grow_mid_walk (void) ++{ ++ mock_server_t *server = mock_server_with_auto_hello (WIRE_VERSION_MAX); ++ mongoc_async_t *async = mongoc_async_new (); ++ bson_t hello_cmd = BSON_INITIALIZER; ++ grow_ctx_t ctx = {0}; ++ ++ mock_server_run (server); ++ ++ BSON_ASSERT (BSON_APPEND_INT32 (&hello_cmd, HANDSHAKE_CMD_LEGACY_HELLO, 1)); ++ ++ ctx.async = async; ++ ctx.cmd = &hello_cmd; ++ for (int i = 0; i < GROW_NSTREAMS; i++) { ++ ctx.streams[i] = get_localhost_stream (mock_server_get_port (server)); ++ } ++ ++ // Appends a third command in `test_grow_initiator` callback. ++ mongoc_async_cmd_new (async, ++ NULL, /* stream, acquired by the initiator. */ ++ false, /* is setup done. */ ++ NULL, /* dns result. */ ++ test_grow_initiator, ++ 0, /* no delay: initiates in the fill loop. */ ++ NULL, /* setup function. */ ++ NULL, /* setup ctx. */ ++ "admin", ++ &hello_cmd, ++ MONGOC_OP_CODE_QUERY, ++ &test_grow_callback, ++ &ctx, ++ TIMEOUT); ++ ++ // Add a trailing command, so the appended command is processed in the same loop pass. ++ mongoc_async_cmd_new (async, ++ ctx.streams[1], ++ false, /* is setup done. */ ++ NULL, /* dns result. */ ++ NULL, /* initiator. */ ++ 0, /* no delay. */ ++ NULL, /* setup function. */ ++ NULL, /* setup ctx. */ ++ "admin", ++ &hello_cmd, ++ MONGOC_OP_CODE_QUERY, ++ &test_grow_callback, ++ &ctx, ++ TIMEOUT); ++ ++ mongoc_async_run (async); ++ ++ BSON_ASSERT (ctx.appended); ++ ASSERT_CMPINT (ctx.nfinished, ==, GROW_NSTREAMS); ++ ++ bson_destroy (&hello_cmd); ++ for (int i = 0; i < GROW_NSTREAMS; i++) { ++ mongoc_stream_destroy (ctx.streams[i]); ++ } ++ mongoc_async_destroy (async); ++ mock_server_destroy (server); ++} ++ + void + test_async_install (TestSuite *suite) + { +@@ -384,4 +506,5 @@ test_async_install (TestSuite *suite) + test_framework_skip_if_windows); + #endif + TestSuite_AddMockServerTest (suite, "/Async/delay", test_hello_delay); ++ TestSuite_AddMockServerTest (suite, "/Async/grow_mid_walk", test_async_grow_mid_walk); + } +-- +2.39.5 + diff -Nru mongo-c-driver-1.30.4/debian/patches/0018_CVE-2026-106428.patch mongo-c-driver-1.30.4/debian/patches/0018_CVE-2026-106428.patch --- mongo-c-driver-1.30.4/debian/patches/0018_CVE-2026-106428.patch 1970-01-01 00:00:00.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/patches/0018_CVE-2026-106428.patch 2026-11-03 21:12:33.000000000 +0000 @@ -0,0 +1,163 @@ +From 57054353fdf79383341de21540a2adab196eb6a4 Mon Sep 17 00:00:00 2001 +From: Kevin Albertson +Date: Wed, 7 Oct 2026 14:35:01 -0400 +Subject: [PATCH] CDRIVER-6370 add tests and fixes for SCRAM parser (#2350) + (#2475) + +* Add test cases for SCRAM step 2 parse errors + +* Add test cases for SCRAM step 3 parse errors + +* Add clarifying comments for ptr increments + +Backported from 9655909e15c83149c0fc68e53f38e696bbd23569 + +Co-authored-by: Connor MacDonald +--- + src/libmongoc/src/mongoc/mongoc-scram.c | 6 +- + src/libmongoc/tests/test-mongoc-scram.c | 84 +++++++++++++++++++++++++ + 2 files changed, 88 insertions(+), 2 deletions(-) + +diff --git a/src/libmongoc/src/mongoc/mongoc-scram.c b/src/libmongoc/src/mongoc/mongoc-scram.c +index 58f787c65f..6867075f32 100644 +--- a/src/libmongoc/src/mongoc/mongoc-scram.c ++++ b/src/libmongoc/src/mongoc/mongoc-scram.c +@@ -612,7 +612,7 @@ _mongoc_scram_step2 (mongoc_scram_t *scram, + + ptr++; + +- if (*ptr != '=') { ++ if (ptr >= inbuf + inbuflen || *ptr != '=') { + bson_set_error (error, + MONGOC_ERROR_SCRAM, + MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, +@@ -621,6 +621,7 @@ _mongoc_scram_step2 (mongoc_scram_t *scram, + goto FAIL; + } + ++ /* This may refer to one-past-end, but memchr with length 0 is well-defined. */ + ptr++; + + const uint8_t *const next_comma = (const uint8_t *) memchr (ptr, ',', (inbuf + inbuflen) - ptr); +@@ -883,7 +884,7 @@ _mongoc_scram_step3 (mongoc_scram_t *scram, + + ptr++; + +- if (*ptr != '=') { ++ if (ptr >= inbuf + inbuflen || *ptr != '=') { + bson_set_error (error, + MONGOC_ERROR_SCRAM, + MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, +@@ -891,6 +892,7 @@ _mongoc_scram_step3 (mongoc_scram_t *scram, + goto FAIL; + } + ++ /* This may refer to one-past-end, but memchr with length 0 is well-defined. */ + ptr++; + + const uint8_t *const next_comma = (const uint8_t *) memchr (ptr, ',', (inbuf + inbuflen) - ptr); +diff --git a/src/libmongoc/tests/test-mongoc-scram.c b/src/libmongoc/tests/test-mongoc-scram.c +index ca36baee3d..cf10ce68b3 100644 +--- a/src/libmongoc/tests/test-mongoc-scram.c ++++ b/src/libmongoc/tests/test-mongoc-scram.c +@@ -122,6 +122,88 @@ test_mongoc_scram_step_nonce_mismatch (void) + _mongoc_scram_destroy (&scram); + } + ++static void ++test_mongoc_scram_step2_invalid_parse_state (void) ++{ ++ const char *responses[] = { ++ "r", /* bare key at end of buffer */ ++ "s", /* bare key at end of buffer */ ++ "i", /* bare key at end of buffer */ ++ "r=YWJjZA==YWJjZA==,s=r6+P1iLmSJvhrRyuFi6Wsg==,i", /* valid fields then bare trailing key */ ++ "r?", /* no '=' after key */ ++ "s+", /* no '=' after key */ ++ "i~", /* no '=' after key */ ++ "r=YWJjZA==YWJjZA==,s=r6+P1iLmSJvhrRyuFi6Wsg==,i-", /* valid fields then no '=' after key */ ++ }; ++ ++ for (size_t idx = 0; idx < sizeof (responses) / sizeof (responses[0]); idx++) { ++ const size_t inbuflen = strlen (responses[idx]); ++ uint8_t *const inbuf = bson_malloc (inbuflen); ++ memcpy (inbuf, responses[idx], inbuflen); ++ ++ mongoc_scram_t scram; ++ _mongoc_scram_init (&scram, MONGOC_CRYPTO_ALGORITHM_SHA_1); ++ _mongoc_scram_set_pass (&scram, "password"); ++ ++ const char *const client_nonce = "YWJjZA=="; ++ bson_strncpy (scram.encoded_nonce, client_nonce, sizeof (scram.encoded_nonce)); ++ scram.encoded_nonce_len = (int32_t) strlen (client_nonce); ++ scram.auth_message = bson_malloc0 (4096); ++ scram.auth_messagemax = 4096; ++ /* Advance to step 2 (step is incremented in _mongoc_scram_step). */ ++ scram.step = 1; ++ ++ uint8_t outbuf[4096] = {0}; ++ uint32_t outbuflen = 0; ++ bson_error_t error; ++ ASSERT (!_mongoc_scram_step (&scram, inbuf, (uint32_t) inbuflen, outbuf, sizeof (outbuf), &outbuflen, &error)); ++ ASSERT_ERROR_CONTAINS (error, ++ MONGOC_ERROR_SCRAM, ++ MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, ++ "SCRAM Failure: invalid parse state in sasl step 2"); ++ ++ _mongoc_scram_destroy (&scram); ++ bson_free (inbuf); ++ } ++} ++ ++static void ++test_mongoc_scram_step3_trailing_key (void) ++{ ++ const char *responses[] = { ++ "e", /* bare key at end of buffer */ ++ "v", /* bare key at end of buffer */ ++ "v=abc,e", /* valid field followed by bare trailing key */ ++ "e?", /* no '=' after key */ ++ "v?", /* no '=' after key */ ++ "v=abc,e?", /* valid field followed by no '=' after key */ ++ }; ++ ++ for (size_t i = 0; i < sizeof (responses) / sizeof (responses[0]); i++) { ++ const size_t inbuflen = strlen (responses[i]); ++ uint8_t *const inbuf = bson_malloc (inbuflen); ++ memcpy (inbuf, responses[i], inbuflen); ++ ++ mongoc_scram_t scram; ++ _mongoc_scram_init (&scram, MONGOC_CRYPTO_ALGORITHM_SHA_1); ++ _mongoc_scram_set_pass (&scram, "password"); ++ /* Advance to step 3 (step is incremented in _mongoc_scram_step). */ ++ scram.step = 2; ++ ++ uint8_t outbuf[4096] = {0}; ++ uint32_t outbuflen = 0; ++ bson_error_t error; ++ ASSERT (!_mongoc_scram_step (&scram, inbuf, (uint32_t) inbuflen, outbuf, sizeof (outbuf), &outbuflen, &error)); ++ ASSERT_ERROR_CONTAINS (error, ++ MONGOC_ERROR_SCRAM, ++ MONGOC_ERROR_SCRAM_PROTOCOL_ERROR, ++ "SCRAM Failure: invalid parse state in sasl step 3"); ++ ++ _mongoc_scram_destroy (&scram); ++ bson_free (inbuf); ++ } ++} ++ + static void + test_mongoc_scram_sasl_prep (void) + { +@@ -779,6 +861,8 @@ test_scram_install (TestSuite *suite) + TestSuite_Add (suite, "/scram/sasl_prep", test_mongoc_scram_sasl_prep); + TestSuite_Add (suite, "/scram/iteration_count", test_mongoc_scram_iteration_count); + TestSuite_Add (suite, "/scram/nonce_mismatch", test_mongoc_scram_step_nonce_mismatch); ++ TestSuite_Add (suite, "/scram/step2_invalid_parse_state", test_mongoc_scram_step2_invalid_parse_state); ++ TestSuite_Add (suite, "/scram/step3_invalid_parse_state", test_mongoc_scram_step3_trailing_key); + TestSuite_Add (suite, "/scram/utf8_char_length", test_mongoc_utf8_char_length); + TestSuite_Add (suite, "/scram/utf8_string_length", test_mongoc_utf8_string_length); + TestSuite_Add (suite, "/scram/utf8_to_unicode", test_mongoc_utf8_to_unicode); +-- +2.39.5 + diff -Nru mongo-c-driver-1.30.4/debian/patches/0019_CVE-2026-106431.patch mongo-c-driver-1.30.4/debian/patches/0019_CVE-2026-106431.patch --- mongo-c-driver-1.30.4/debian/patches/0019_CVE-2026-106431.patch 1970-01-01 00:00:00.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/patches/0019_CVE-2026-106431.patch 2026-11-03 21:12:33.000000000 +0000 @@ -0,0 +1,63 @@ +From 70d96b8f28974e02cc966a00953dc3d8c308b8ff Mon Sep 17 00:00:00 2001 +From: mdb-ad <198671546+mdb-ad@users.noreply.github.com> +Date: Wed, 23 Sep 2026 16:08:13 -0700 +Subject: [PATCH] fix empty document off-by-one writes + +--- + src/libbson/src/bson/bson-writer.c | 2 +- + src/libbson/tests/test-writer.c | 21 +++++++++++++++++++++ + 2 files changed, 22 insertions(+), 1 deletion(-) + +diff --git a/src/libbson/src/bson/bson-writer.c b/src/libbson/src/bson/bson-writer.c +index 40721eb549..858a9c374b 100644 +--- a/src/libbson/src/bson/bson-writer.c ++++ b/src/libbson/src/bson/bson-writer.c +@@ -201,7 +201,7 @@ bson_writer_begin (bson_writer_t *writer, /* IN */ + *writer->buf = writer->realloc_func (*writer->buf, *writer->buflen, writer->realloc_func_ctx); + } + +- memset ((*writer->buf) + writer->offset + 1, 0, 5); ++ memset ((*writer->buf) + writer->offset + 1, 0, 4); + (*writer->buf)[writer->offset] = 5; + + *bson = &writer->b; +diff --git a/src/libbson/tests/test-writer.c b/src/libbson/tests/test-writer.c +index 7566137140..44159ab832 100644 +--- a/src/libbson/tests/test-writer.c ++++ b/src/libbson/tests/test-writer.c +@@ -178,6 +178,26 @@ test_bson_writer_null_realloc_2 (void) + bson_free (buf); + } + ++/* bson_writer_begin must not write past the 5 bytes of an empty document when ++ * the buffer has exactly enough room for it. */ ++static void ++test_bson_writer_exact_fit (void) ++{ ++ uint8_t storage[6] = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}; ++ const uint8_t expected[] = {5, 0, 0, 0, 0, 0xAA}; ++ uint8_t *buf = storage; ++ size_t buflen = 5; /* Final byte of storage is a sentinel. */ ++ bson_writer_t *writer; ++ bson_t *b; ++ ++ writer = bson_writer_new (&buf, &buflen, 0, NULL, NULL); ++ BSON_ASSERT (bson_writer_begin (writer, &b)); ++ bson_writer_end (writer); ++ bson_writer_destroy (writer); ++ ++ BSON_ASSERT (memcmp (storage, expected, sizeof expected) == 0); ++} ++ + void + test_writer_install (TestSuite *suite) + { +@@ -186,4 +206,5 @@ test_writer_install (TestSuite *suite) + TestSuite_Add (suite, "/bson/writer/empty_sequence", test_bson_writer_empty_sequence); + TestSuite_Add (suite, "/bson/writer/null_realloc", test_bson_writer_null_realloc); + TestSuite_Add (suite, "/bson/writer/null_realloc_2", test_bson_writer_null_realloc_2); ++ TestSuite_Add (suite, "/bson/writer/exact_fit", test_bson_writer_exact_fit); + } +-- +2.39.5 + diff -Nru mongo-c-driver-1.30.4/debian/patches/0020_CVE-2026-106438.patch mongo-c-driver-1.30.4/debian/patches/0020_CVE-2026-106438.patch --- mongo-c-driver-1.30.4/debian/patches/0020_CVE-2026-106438.patch 1970-01-01 00:00:00.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/patches/0020_CVE-2026-106438.patch 2026-11-03 21:12:33.000000000 +0000 @@ -0,0 +1,78 @@ +From 12930599bad296e8a42042677478279fd9e903f7 Mon Sep 17 00:00:00 2001 +From: mdb-ad <198671546+mdb-ad@users.noreply.github.com> +Date: Fri, 25 Sep 2026 15:15:17 -0700 +Subject: [PATCH] ignore leading zeros for significant digit count + +--- + src/libbson/src/bson/bson-decimal128.c | 2 +- + src/libbson/tests/test-decimal128.c | 27 ++++++++++++++++++++++++++ + 2 files changed, 28 insertions(+), 1 deletion(-) + +diff --git a/src/libbson/src/bson/bson-decimal128.c b/src/libbson/src/bson/bson-decimal128.c +index 135ea6a640..e0ceb4c0d7 100644 +--- a/src/libbson/src/bson/bson-decimal128.c ++++ b/src/libbson/src/bson/bson-decimal128.c +@@ -666,7 +666,7 @@ bson_decimal128_from_string_w_len (const char *string, /* IN */ + } + + if (ndigits_stored < ndigits) { +- if (string[ndigits - 1 + includes_sign + saw_radix] - '0' != 0 && significant_digits != 0) { ++ if (string[first_nonzero + ndigits - 1 + includes_sign + saw_radix] - '0' != 0 && significant_digits != 0) { + BSON_DECIMAL128_SET_NAN (*dec); + return false; + } +diff --git a/src/libbson/tests/test-decimal128.c b/src/libbson/tests/test-decimal128.c +index c7daaef7d2..8d04eb338c 100644 +--- a/src/libbson/tests/test-decimal128.c ++++ b/src/libbson/tests/test-decimal128.c +@@ -342,6 +342,14 @@ test_decimal128_from_string__invalid_inputs (void) + ASSERT (IS_NAN (dec)); + ASSERT (!bson_decimal128_from_string_w_len ("e+02", 4, &dec)); + ASSERT (IS_NAN (dec)); ++ ++ /* Inexact: more than 34 significant digits after leading zeros. */ ++ ASSERT (!bson_decimal128_from_string ("0100000000000000000000000000000000001", &dec)); ++ ASSERT (IS_NAN (dec)); ++ ASSERT (!bson_decimal128_from_string ("-00100000000000000000000000000000000001", &dec)); ++ ASSERT (IS_NAN (dec)); ++ ASSERT (!bson_decimal128_from_string ("0.0100000000000000000000000000000000001E+40", &dec)); ++ ASSERT (IS_NAN (dec)); + } + + +@@ -723,6 +731,22 @@ test_decimal128_from_string_w_len__special (void) + ASSERT (decimal128_equal (&negative_number, 0xb040000000000000, 0x002bdc545d6b4b87)); + } + ++static void ++test_decimal128_from_string__leading_zeros_excess_digits (void) ++{ ++ bson_decimal128_t dec; ++ bson_decimal128_t expected; ++ ++ /* Excess trailing zeros after leading zeros are exact and must be accepted. */ ++ ASSERT (bson_decimal128_from_string ("010000000000000000000000000000000000", &dec)); ++ ASSERT (bson_decimal128_from_string ("1.000000000000000000000000000000000E+34", &expected)); ++ ASSERT (dec.high == expected.high && dec.low == expected.low); ++ ++ ASSERT (bson_decimal128_from_string ("001234567890123456789012345678901234000", &dec)); ++ ASSERT (bson_decimal128_from_string ("1234567890123456789012345678901234E+3", &expected)); ++ ASSERT (dec.high == expected.high && dec.low == expected.low); ++} ++ + void + test_decimal128_install (TestSuite *suite) + { +@@ -732,6 +756,9 @@ test_decimal128_install (TestSuite *suite) + TestSuite_Add (suite, "/bson/decimal128/to_string/scientific", test_decimal128_to_string__scientific); + TestSuite_Add (suite, "/bson/decimal128/to_string/zero", test_decimal128_to_string__zeros); + TestSuite_Add (suite, "/bson/decimal128/from_string/invalid", test_decimal128_from_string__invalid_inputs); ++ TestSuite_Add (suite, ++ "/bson/decimal128/from_string/leading_zeros_excess_digits", ++ test_decimal128_from_string__leading_zeros_excess_digits); + TestSuite_Add (suite, "/bson/decimal128/from_string/nan", test_decimal128_from_string__nan); + TestSuite_Add (suite, "/bson/decimal128/from_string/infinity", test_decimal128_from_string__infinity); + TestSuite_Add (suite, "/bson/decimal128/from_string/basic", test_decimal128_from_string__simple); +-- +2.39.5 + diff -Nru mongo-c-driver-1.30.4/debian/patches/0021_CVE-2026-106437.patch mongo-c-driver-1.30.4/debian/patches/0021_CVE-2026-106437.patch --- mongo-c-driver-1.30.4/debian/patches/0021_CVE-2026-106437.patch 1970-01-01 00:00:00.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/patches/0021_CVE-2026-106437.patch 2026-11-03 21:12:33.000000000 +0000 @@ -0,0 +1,69 @@ +From b7f1f5742351741c068020196fd88f196551aad2 Mon Sep 17 00:00:00 2001 +From: mdb-ad <198671546+mdb-ad@users.noreply.github.com> +Date: Fri, 25 Sep 2026 15:30:26 -0700 +Subject: [PATCH] enforce minimum size of 5 in bson_reserve_buffer + +--- + src/libbson/doc/bson_reserve_buffer.rst | 4 ++-- + src/libbson/src/bson/bson.c | 5 +++++ + src/libbson/tests/test-bson.c | 6 ++++++ + 3 files changed, 13 insertions(+), 2 deletions(-) + +diff --git a/src/libbson/doc/bson_reserve_buffer.rst b/src/libbson/doc/bson_reserve_buffer.rst +index 16b31c96d1..9fea5be91e 100644 +--- a/src/libbson/doc/bson_reserve_buffer.rst ++++ b/src/libbson/doc/bson_reserve_buffer.rst +@@ -15,7 +15,7 @@ Parameters + ---------- + + * ``bson``: An initialized :symbol:`bson_t`. +-* ``total_size``: The length in bytes of the new buffer. ++* ``total_size``: The length in bytes of the new buffer. Must be at least 5 (the size of an empty BSON document). + + Description + ----------- +@@ -32,7 +32,7 @@ The ``bson_t`` document length and buffer size limit are both set to ``total_siz + Returns + ------- + +-A pointer to the internal buffer, which is at least ``total_size`` bytes, or NULL if the space could not be allocated. ++A pointer to the internal buffer, which is at least ``total_size`` bytes, or NULL if ``total_size`` is less than 5 or the space could not be allocated. + + Example + ------- +diff --git a/src/libbson/src/bson/bson.c b/src/libbson/src/bson/bson.c +index fbbca78784..7962652ac8 100644 +--- a/src/libbson/src/bson/bson.c ++++ b/src/libbson/src/bson/bson.c +@@ -2174,6 +2174,11 @@ bson_reserve_buffer (bson_t *bson, uint32_t total_size) + return NULL; + } + ++ /* The smallest valid BSON document is 5 bytes. */ ++ if (total_size < 5u) { ++ return NULL; ++ } ++ + if (total_size > bson->len) { + if ((size_t) total_size > BSON_MAX_SIZE) { + return NULL; +diff --git a/src/libbson/tests/test-bson.c b/src/libbson/tests/test-bson.c +index 466f430a40..29bbd9fe4b 100644 +--- a/src/libbson/tests/test-bson.c ++++ b/src/libbson/tests/test-bson.c +@@ -1595,6 +1595,12 @@ test_bson_reserve_buffer_errors (void) + uint8_t data[5] = {0}; + uint32_t len_le; + ++ /* too small: less than the minimum BSON document size */ ++ for (uint32_t i = 0u; i < 5u; i++) { ++ ASSERT (!bson_reserve_buffer (&bson, i)); ++ ASSERT_CMPUINT32(bson.len, ==, 5u); ++ } ++ + /* too big */ + ASSERT (!bson_reserve_buffer (&bson, (uint32_t) (BSON_MAX_SIZE + 1u))); + /* exactly the maximum size */ +-- +2.39.5 + diff -Nru mongo-c-driver-1.30.4/debian/patches/0022_CVE-2026-120955.patch mongo-c-driver-1.30.4/debian/patches/0022_CVE-2026-120955.patch --- mongo-c-driver-1.30.4/debian/patches/0022_CVE-2026-120955.patch 1970-01-01 00:00:00.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/patches/0022_CVE-2026-120955.patch 2026-11-03 21:12:33.000000000 +0000 @@ -0,0 +1,98 @@ +From 811030d131c4caba8dc9645c54319d692ca7b851 Mon Sep 17 00:00:00 2001 +From: Sabrina Chang +Date: Mon, 2 Nov 2026 08:30:42 -0500 +Subject: [PATCH] CDRIVER-6494 error if gridfs download_stream is null + +--- + src/libmongoc/src/mongoc/mongoc-gridfs-bucket.c | 4 + + src/libmongoc/tests/test-mongoc-gridfs-bucket.c | 59 ++++++++++++++++++++++++ + 2 files changed, 63 insertions(+) + +--- a/src/libmongoc/src/mongoc/mongoc-gridfs-bucket.c ++++ b/src/libmongoc/src/mongoc/mongoc-gridfs-bucket.c +@@ -408,6 +408,10 @@ + /* Make the download stream */ + download_stream = mongoc_gridfs_bucket_open_download_stream (bucket, file_id, error); + ++ if (!download_stream) { ++ return false; ++ } ++ + while ((bytes_read = mongoc_stream_read (download_stream, buf, 256, 1, 0)) > 0) { + bytes_written = mongoc_stream_write (destination, buf, bytes_read, 0); + if (bytes_written < 0) { +--- a/src/libmongoc/tests/test-mongoc-gridfs-bucket.c ++++ b/src/libmongoc/tests/test-mongoc-gridfs-bucket.c +@@ -1051,6 +1051,64 @@ + mongoc_client_destroy (client); + } + ++// Regression test for CDRIVER-6494. ++static void ++test_download_to_stream_errors (void) ++{ ++ mongoc_client_t *client = test_framework_new_default_client (); ++ bson_error_t error; ++ ++ mongoc_database_t *db = mongoc_client_get_database (client, "test_download_to_stream_errors"); ++ mongoc_database_drop (db, NULL); ++ ++ mongoc_gridfs_bucket_t *bucket = mongoc_gridfs_bucket_new (db, NULL, NULL, &error); ++ ASSERT_OR_PRINT (bucket, error); ++ ++ // Any valid stream will do: it is never written to when the download fails. ++ mongoc_stream_t *destination = mongoc_stream_file_new_for_path (BSON_BINARY_DIR "/test1.bson", O_RDONLY, 0); ++ ASSERT (destination); ++ ++ // Test a file that does not exist: ++ { ++ const bson_value_t file_id = {.value_type = BSON_TYPE_INT32, .value.v_int32 = 1}; ++ ++ ASSERT (!mongoc_gridfs_bucket_download_to_stream (bucket, &file_id, destination, &error)); ++ ASSERT_ERROR_CONTAINS ( ++ error, MONGOC_ERROR_GRIDFS, MONGOC_ERROR_GRIDFS_BUCKET_FILE_NOT_FOUND, "No file with given id exists"); ++ } ++ ++ // Test a file with a zero chunkSize: ++ { ++ bson_t *doc = tmp_bson (BSON_STR ({ ++ "_id" : 2, ++ "filename" : "foo.txt", ++ "length" : 1000, ++ "chunkSize" : 0, // Zero! ++ "uploadDate" : {"$date" : 1234567890000} ++ })); ++ ++ bson_iter_t id_iter; ++ ASSERT (bson_iter_init_find (&id_iter, doc, "_id")); ++ const bson_value_t *id_value = bson_iter_value (&id_iter); ++ ++ // Insert manually: ++ { ++ mongoc_collection_t *files = mongoc_database_get_collection (db, "fs.files"); ++ ASSERT_OR_PRINT (mongoc_collection_insert_one (files, doc, NULL, NULL, &error), error); ++ mongoc_collection_destroy (files); ++ } ++ ++ ASSERT (!mongoc_gridfs_bucket_download_to_stream (bucket, id_value, destination, &error)); ++ ASSERT_ERROR_CONTAINS (error, MONGOC_ERROR_GRIDFS, MONGOC_ERROR_GRIDFS_CORRUPT, "invalid chunk size"); ++ } ++ ++ mongoc_stream_destroy (destination); ++ mongoc_gridfs_bucket_destroy (bucket); ++ mongoc_database_drop (db, NULL); ++ mongoc_database_destroy (db); ++ mongoc_client_destroy (client); ++} ++ + // GridFS prose test 1: "Aborting an upload with an injected file ID does not delete other files' chunks". + static void + prose_test_1 (void *unused) +@@ -1141,6 +1199,7 @@ + test_framework_skip_if_no_sessions, + test_framework_skip_if_no_crypto); + TestSuite_AddLive (suite, "/gridfs/options", test_gridfs_bucket_opts); ++ TestSuite_AddLive (suite, "/gridfs/download_to_stream_errors", test_download_to_stream_errors); + TestSuite_AddFull (suite, + "/gridfs/prose_test_1", + prose_test_1, diff -Nru mongo-c-driver-1.30.4/debian/patches/0023_CVE-2026-120678.patch mongo-c-driver-1.30.4/debian/patches/0023_CVE-2026-120678.patch --- mongo-c-driver-1.30.4/debian/patches/0023_CVE-2026-120678.patch 1970-01-01 00:00:00.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/patches/0023_CVE-2026-120678.patch 2026-11-03 21:12:33.000000000 +0000 @@ -0,0 +1,133 @@ +From 40733d2100cbed890008042e8bb8ab80c8f9249f Mon Sep 17 00:00:00 2001 +From: Kevin Albertson +Date: Mon, 2 Nov 2026 08:30:42 -0500 +Subject: [PATCH] CDRIVER-6521 fix possible leaks on decompression failure + +--- + src/libmongoc/src/mongoc/mongoc-async-cmd.c | 4 +- + src/libmongoc/src/mongoc/mongoc-cluster.c | 16 ++++++-- + src/libmongoc/tests/test-mongoc-cluster.c | 45 +++++++++++++++++++++ + 3 files changed, 60 insertions(+), 5 deletions(-) + +diff --git a/src/libmongoc/src/mongoc/mongoc-async-cmd.c b/src/libmongoc/src/mongoc/mongoc-async-cmd.c +index 7c66cf2830..3246516024 100644 +--- a/src/libmongoc/src/mongoc/mongoc-async-cmd.c ++++ b/src/libmongoc/src/mongoc/mongoc-async-cmd.c +@@ -437,8 +437,8 @@ _mongoc_async_cmd_phase_recv_rpc (mongoc_async_cmd_t *acmd) + } + mcd_rpc_message_ingress (acmd->rpc); + +- void *decompressed_data; +- size_t decompressed_data_len; ++ void *decompressed_data = NULL; ++ size_t decompressed_data_len = 0u; + + if (!mcd_rpc_message_decompress_if_necessary (acmd->rpc, &decompressed_data, &decompressed_data_len)) { + bson_set_error (&acmd->error, +diff --git a/src/libmongoc/src/mongoc/mongoc-cluster.c b/src/libmongoc/src/mongoc/mongoc-cluster.c +index 1f3e119cf5..2f4fb4b4e2 100644 +--- a/src/libmongoc/src/mongoc/mongoc-cluster.c ++++ b/src/libmongoc/src/mongoc/mongoc-cluster.c +@@ -3445,6 +3445,10 @@ mcd_rpc_message_decompress (mcd_rpc_message *rpc, void **data, size_t *data_len) + BSON_ASSERT_PARAM (data); + BSON_ASSERT_PARAM (data_len); + ++ // Always "reset" out-parameters first. See also `mcd_rpc_message_decompress_if_necessary()`. ++ *data = NULL; ++ *data_len = 0u; ++ + BSON_ASSERT (mcd_rpc_header_get_op_code (rpc) == MONGOC_OP_CODE_COMPRESSED); + + // msgHeader consists of four int32 fields. +@@ -3514,12 +3518,18 @@ mcd_rpc_message_decompress (mcd_rpc_message *rpc, void **data, size_t *data_len) + return false; + } + ++ mcd_rpc_message_reset (rpc); ++ ++ // Correctly handle potential failure condition. ++ if (!mcd_rpc_message_from_data_in_place (rpc, ptr, original_message_length, NULL)) { ++ bson_free (ptr); ++ return false; ++ } ++ + *data_len = original_message_length; + *data = ptr; // Ownership transfer. + +- mcd_rpc_message_reset (rpc); +- +- return mcd_rpc_message_from_data_in_place (rpc, *data, *data_len, NULL); ++ return true; + } + + bool +diff --git a/src/libmongoc/tests/test-mongoc-cluster.c b/src/libmongoc/tests/test-mongoc-cluster.c +index 5995fcddbc..eb8784d2bc 100644 +--- a/src/libmongoc/tests/test-mongoc-cluster.c ++++ b/src/libmongoc/tests/test-mongoc-cluster.c +@@ -1,3 +1,4 @@ ++#include + #include + #include + +@@ -111,6 +112,49 @@ test_get_max_msg_size (void) + mongoc_client_pool_destroy (pool); + } + ++// clang-format off ++#define TEST_DATA_OP_COMPRESSED \ ++ /* */ /* header (16 bytes) */ \ ++ /* 0 */ 0x2d, 0x00, 0x00, 0x00, /* messageLength (45) */ \ ++ /* 4 */ 0x04, 0x03, 0x02, 0x01, /* requestID (16909060) */ \ ++ /* 8 */ 0x08, 0x07, 0x06, 0x05, /* responseTo (84281096) */ \ ++ /* 12 */ 0xdc, 0x07, 0x00, 0x00, /* opCode (2012: OP_COMPRESSED) */ \ ++ /* */ \ ++ /* */ /* OP_COMPRESSED fields (9 bytes) */ \ ++ /* 16 */ 0xdd, 0x07, 0x00, 0x00, /* originalOpcode (2013: OP_MSG) */ \ ++ /* 20 */ 0x14, 0x00, 0x00, 0x00, /* uncompressedSize (20) */ \ ++ /* 24 */ 0x00, /* compressorId (0: noop) */ \ ++ /* */ \ ++ /* */ /* compressedMessage (20 bytes) */ \ ++ /* 25 */ 0x00, 0x00, 0x00, 0x00, /* flagBits (MONGOC_OP_MSG_FLAG_NONE) */ \ ++ /* 29 */ 0x00, /* Kind 0: Body */ \ ++ /* 30 */ 0x0f, 0x00, 0x00, 0x00, /* (15 bytes) { */ \ ++ /* 34 */ 0x10, /* (int32) */ \ ++ /* 35 */ 0x6b, 0x69, 0x6e, 0x64, 0x00, /* 'kind': */ \ ++ /* 40 */ 0x00, 0x00, 0x00, 0x00, /* 0 */ \ ++ /* 44 */ 0x00 /* } */ \ ++ /* 45 */ ++// clang-format on ++ ++static void ++test_decompress_invalid_message (void) ++{ ++ uint8_t data[] = {TEST_DATA_OP_COMPRESSED}; ++ ++ data[25] = 0xffu; // OP_MSG flagBits: invalid. ++ ++ mcd_rpc_message *const rpc = mcd_rpc_message_from_data (data, sizeof (data), NULL); ++ ASSERT (rpc); ++ ++ void *decompressed = data; // Set to a non-NULL address to verify reset-to-NULL on failure. ++ size_t decompressed_len = sizeof (data); ++ ASSERT (!mcd_rpc_message_decompress (rpc, &decompressed, &decompressed_len)); ++ ASSERT (decompressed == NULL); ++ ASSERT (decompressed_len == 0u); ++ ++ mcd_rpc_message_destroy (rpc); ++} ++ + + #define ASSERT_CURSOR_ERR() \ + do { \ +@@ -1785,6 +1829,7 @@ test_cluster_install (TestSuite *suite) + + TestSuite_AddLive (suite, "/Cluster/test_get_max_bson_obj_size", test_get_max_bson_obj_size); + TestSuite_AddLive (suite, "/Cluster/test_get_max_msg_size", test_get_max_msg_size); ++ TestSuite_Add (suite, "/Cluster/decompress/invalid_message", test_decompress_invalid_message); + TestSuite_AddFull (suite, + "/Cluster/disconnect/single", + test_cluster_node_disconnect_single, +-- +2.39.5 + diff -Nru mongo-c-driver-1.30.4/debian/patches/0024_CVE-2026-120684.patch mongo-c-driver-1.30.4/debian/patches/0024_CVE-2026-120684.patch --- mongo-c-driver-1.30.4/debian/patches/0024_CVE-2026-120684.patch 1970-01-01 00:00:00.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/patches/0024_CVE-2026-120684.patch 2026-11-03 21:12:33.000000000 +0000 @@ -0,0 +1,141 @@ +From edde153ac24b5c68d768f8a3c8dd0a435000466c Mon Sep 17 00:00:00 2001 +From: Kevin Albertson +Date: Mon, 2 Nov 2026 08:30:42 -0500 +Subject: [PATCH] CDRIVER-6508 zero-fill gap in gridfs page writes + +--- + .../src/mongoc/mongoc-gridfs-file-page.c | 6 ++ + src/libmongoc/tests/test-mongoc-gridfs.c | 93 +++++++++++++++++++ + 2 files changed, 99 insertions(+) + +diff --git a/src/libmongoc/src/mongoc/mongoc-gridfs-file-page.c b/src/libmongoc/src/mongoc/mongoc-gridfs-file-page.c +index 7606406791..2b803a41a3 100644 +--- a/src/libmongoc/src/mongoc/mongoc-gridfs-file-page.c ++++ b/src/libmongoc/src/mongoc/mongoc-gridfs-file-page.c +@@ -115,6 +115,12 @@ _mongoc_gridfs_file_page_write (mongoc_gridfs_file_page_t *page, const void *src + if (!page->buf) { + page->buf = (uint8_t *) bson_malloc (page->chunk_size); + memcpy (page->buf, page->read_buf, BSON_MIN (page->chunk_size, page->len)); ++ ++ /* If writing at a position past the page's stored data, zero data ++ * between. */ ++ if (page->offset > page->len) { ++ memset (page->buf + page->len, '\0', page->offset - page->len); ++ } + } + + /* Copy bytes and adjust the page position */ +diff --git a/src/libmongoc/tests/test-mongoc-gridfs.c b/src/libmongoc/tests/test-mongoc-gridfs.c +index 9f4d0f9852..6a167c9b06 100644 +--- a/src/libmongoc/tests/test-mongoc-gridfs.c ++++ b/src/libmongoc/tests/test-mongoc-gridfs.c +@@ -506,6 +506,98 @@ test_find_one_with_opts_limit (void) + } + + ++// Regression test for CDRIVER-6508: test writing to the end of a page when the file metadata has an incorrect length. ++static void ++test_append_to_short_final_chunk (void) ++{ ++ mock_server_t *server = mock_server_with_auto_hello (WIRE_VERSION_MIN); ++ mock_server_run (server); ++ mongoc_client_t *client = test_framework_client_new_from_uri (mock_server_get_uri (server), NULL); ++ mongoc_client_set_error_api (client, 2); ++ ++ mongoc_gridfs_t *gridfs = _get_gridfs (server, client, MONGOC_QUERY_SECONDARY_OK); ++ ++ // File metadata incorrectly claims a length of 127 bytes. But the file only contains 1 byte. ++ bson_error_t error; ++ future_t *future = ++ future_gridfs_find_one_with_opts (gridfs, tmp_bson (BSON_STR ({"filename" : "gap"})), NULL, &error); ++ request_t *request = mock_server_receives_msg ( ++ server, ++ MONGOC_MSG_NONE, ++ tmp_bson (BSON_STR ({"$db" : "db", "find" : "fs.files", "filter" : {"filename" : "gap"}, "limit" : 1}))); ++ reply_to_op_msg_request (request, MONGOC_MSG_NONE, tmp_bson (BSON_STR ({ ++ "ok" : 1, ++ "cursor" : { ++ "id" : {"$numberLong" : "0"}, ++ "ns" : "db.fs.files", ++ "firstBatch" : [ { ++ "_id" : {"$oid" : "6902a22f0000000000000001"}, ++ "length" : {"$numberLong" : "127"}, ++ "chunkSize" : 128, ++ "filename" : "gap", ++ "uploadDate" : {"$date" : 0} ++ } ] ++ } ++ }))); ++ mongoc_gridfs_file_t *file = future_get_mongoc_gridfs_file_ptr (future); ++ ASSERT (file); ++ future_destroy (future); ++ request_destroy (request); ++ ++ // Seek to end of file and append. Append two so the writev fills the first page with the first byte and flushes ++ // before writing the second byte in a new page. ++ ASSERT_CMPINT (mongoc_gridfs_file_seek (file, 0, SEEK_END), ==, 0); ++ char append[2] = {'X', 'V'}; ++ mongoc_iovec_t iov = {.iov_base = (void *) append, .iov_len = sizeof append}; ++ future = future_gridfs_file_writev (file, &iov, 1, 0); ++ ++ // writev loads the bad page: ++ request = ++ mock_server_receives_msg (server, MONGOC_MSG_NONE, tmp_bson (BSON_STR ({"$db" : "db", "find" : "fs.chunks"}))); ++ reply_to_op_msg_request ( ++ request, MONGOC_MSG_NONE, tmp_bson (BSON_STR ({ ++ "ok" : 1, ++ "cursor" : { ++ "id" : {"$numberLong" : "0"}, ++ "ns" : "db.fs.chunks", ++ "firstBatch" : [ {"n" : 0, "data" : {"$binary" : {"subType" : "0", "base64" : "QQ=="}}} ] ++ } ++ }))); ++ request_destroy (request); ++ ++ // The first page fills, so writev flushes to fs.chunks: ++ request = mock_server_receives_request (server); ++ ASSERT (request); ++ ASSERT_MATCH (request_get_doc (request, 0), BSON_STR ({"update" : "fs.chunks"})); // Command body. ++ const bson_t *update_op = request_get_doc (request, 1); // Update operation. ++ ++ bson_value_t data; ++ bson_lookup_value (update_op, "u.data", &data); ++ ASSERT_CMPINT (data.value_type, ==, BSON_TYPE_BINARY); ++ const uint8_t *chunk = data.value.v_binary.data; ++ const uint32_t chunk_len = data.value.v_binary.data_len; ++ ++ ASSERT_CMPUINT32 (chunk_len, ==, 128u); ++ // The one stored byte is copied in, and the appended byte lands at the declared end of file. ++ ASSERT_CMPUINT32 ((uint32_t) chunk[0], ==, (uint32_t) 'A'); ++ ASSERT_CMPUINT32 ((uint32_t) chunk[127], ==, (uint32_t) 'X'); ++ // The gap in between must be zeros. ++ for (uint32_t i = 1; i < 127; i++) { ++ ASSERT_CMPUINT32 ((uint32_t) chunk[i], ==, 0u); ++ } ++ bson_value_destroy (&data); ++ ++ reply_to_request_with_ok_and_destroy (request); ++ ASSERT_CMPSSIZE_T (future_get_ssize_t (future), ==, (ssize_t) sizeof append); ++ future_destroy (future); ++ ++ mongoc_gridfs_file_destroy (file); ++ mongoc_gridfs_destroy (gridfs); ++ mongoc_client_destroy (client); ++ mock_server_destroy (server); ++} ++ ++ + static void + test_properties (void) + { +@@ -1777,6 +1869,7 @@ test_gridfs_install (TestSuite *suite) + TestSuite_AddLive (suite, "/gridfs_old/find_one_empty", test_find_one_empty); + TestSuite_AddLive (suite, "/gridfs_old/find_with_opts", test_find_with_opts); + TestSuite_AddMockServerTest (suite, "/gridfs_old/find_one_with_opts/limit", test_find_one_with_opts_limit); ++ TestSuite_AddMockServerTest (suite, "/gridfs_old/append_to_short_final_chunk", test_append_to_short_final_chunk); + TestSuite_AddLive (suite, "/gridfs_old/properties", test_properties); + TestSuite_AddLive (suite, "/gridfs_old/empty", test_empty); + TestSuite_AddLive (suite, "/gridfs_old/read", test_read); +-- +2.39.5 + diff -Nru mongo-c-driver-1.30.4/debian/patches/0025_CDRIVER-6401.patch mongo-c-driver-1.30.4/debian/patches/0025_CDRIVER-6401.patch --- mongo-c-driver-1.30.4/debian/patches/0025_CDRIVER-6401.patch 1970-01-01 00:00:00.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/patches/0025_CDRIVER-6401.patch 2026-11-03 21:12:33.000000000 +0000 @@ -0,0 +1,216 @@ +From 9e5423f882089c2fbede5cafd5a1fee1bab58780 Mon Sep 17 00:00:00 2001 +From: Kevin Albertson +Date: Mon, 2 Nov 2026 08:30:42 -0500 +Subject: [PATCH] CDRIVER-6401 fix handling of cursor namespace when no dot + separator (#2450) + +--- + src/libmongoc/src/mongoc/mongoc-cursor.c | 27 ++++- + src/libmongoc/tests/test-mongoc-cursor.c | 125 +++++++++++++++++++++++ + 2 files changed, 150 insertions(+), 2 deletions(-) + +diff --git a/src/libmongoc/src/mongoc/mongoc-cursor.c b/src/libmongoc/src/mongoc/mongoc-cursor.c +index 5f72badd5b..c422d59d59 100644 +--- a/src/libmongoc/src/mongoc/mongoc-cursor.c ++++ b/src/libmongoc/src/mongoc/mongoc-cursor.c +@@ -550,6 +550,9 @@ mongoc_cursor_destroy (mongoc_cursor_t *cursor) + } + } else if (cursor->client_generation == cursor->client->generation) { + if (cursor->cursor_id) { ++ /* Invariant: a non-zero cursor ID has a namespace with a collection. ++ * Enforced in `_mongoc_cursor_start_reading_response`. */ ++ BSON_ASSERT (cursor->dblen < cursor->nslen); + db = bson_strndup (cursor->ns, cursor->dblen); + + _mongoc_client_kill_cursor (cursor->client, +@@ -1611,7 +1614,7 @@ _mongoc_cursor_start_reading_response (mongoc_cursor_t *cursor, mongoc_cursor_re + { + bson_iter_t iter; + bson_iter_t child; +- const char *ns; ++ const char *ns = NULL; + uint32_t nslen; + bool in_batch = false; + +@@ -1622,7 +1625,6 @@ _mongoc_cursor_start_reading_response (mongoc_cursor_t *cursor, mongoc_cursor_re + cursor->cursor_id = bson_iter_as_int64 (&child); + } else if (BSON_ITER_IS_KEY (&child, "ns")) { + ns = bson_iter_utf8 (&child, &nslen); +- _mongoc_set_cursor_ns (cursor, ns, nslen); + } else if (BSON_ITER_IS_KEY (&child, "firstBatch") || BSON_ITER_IS_KEY (&child, "nextBatch")) { + if (BSON_ITER_HOLDS_ARRAY (&child) && bson_iter_recurse (&child, &response->batch_iter)) { + in_batch = true; +@@ -1640,6 +1642,27 @@ _mongoc_cursor_start_reading_response (mongoc_cursor_t *cursor, mongoc_cursor_re + cursor->client_session = NULL; + } + ++ if (ns) { ++ /* Expect a reply namespace (if present) to have the form ".". ++ * Note: a database aggregate cursor returns a namespace like ".$cmd.aggregate". */ ++ const char *const dot = memchr (ns, '.', nslen); ++ const bool has_collection = dot && (uint32_t) (dot - ns) + 1 < nslen; ++ if (has_collection) { ++ _mongoc_set_cursor_ns (cursor, ns, nslen); ++ } else if (cursor->cursor_id) { ++ /* Set 0 cursor ID to prevent sending killCursors. */ ++ cursor->cursor_id = 0; ++ return false; ++ } ++ } ++ ++ /* A non-zero cursor ID requires a collection to send "getMore" and "killCursors". */ ++ if (cursor->cursor_id && cursor->dblen + 1 >= cursor->nslen) { ++ /* Set 0 cursor ID to prevent sending killCursors. */ ++ cursor->cursor_id = 0; ++ return false; ++ } ++ + return in_batch; + } + +diff --git a/src/libmongoc/tests/test-mongoc-cursor.c b/src/libmongoc/tests/test-mongoc-cursor.c +index 288abb774c..9f95a6a8d2 100644 +--- a/src/libmongoc/tests/test-mongoc-cursor.c ++++ b/src/libmongoc/tests/test-mongoc-cursor.c +@@ -2109,6 +2109,130 @@ test_empty_final_batch (void) + } + + ++// Test server replies with an unusable cursor namespace. Regression test for CDRIVER-6401. ++static void ++test_cursor_bad_ns(void) ++{ ++ mock_server_t *server = mock_server_with_auto_hello(WIRE_VERSION_MIN); ++ mock_server_run(server); ++ ++ mongoc_client_t *client = test_framework_client_new_from_uri(mock_server_get_uri(server), NULL); ++ const bson_t *doc; ++ bson_error_t error; ++ ++ // Test "ns" without "." ++ { ++ mongoc_collection_t *collection = mongoc_client_get_collection(client, "db", "coll"); ++ mongoc_cursor_t *cursor = mongoc_collection_find_with_opts(collection, tmp_bson("{}"), NULL, NULL); ++ ++ future_t *future = future_cursor_next(cursor, &doc); ++ request_t *request = mock_server_receives_msg(server, MONGOC_MSG_NONE, tmp_bson("{'find': 'coll', '$db': 'db'}")); ++ reply_to_op_msg_request( ++ request, ++ MONGOC_MSG_NONE, ++ tmp_bson( ++ BSON_STR({"ok" : 1, "cursor" : {"id" : {"$numberLong" : "1234"}, "ns" : "db", "firstBatch" : [ {} ]}}))); ++ ++ ASSERT(!future_get_bool(future)); ++ ASSERT(mongoc_cursor_error(cursor, &error)); ++ ASSERT_ERROR_CONTAINS( ++ error, MONGOC_ERROR_PROTOCOL, MONGOC_ERROR_PROTOCOL_INVALID_REPLY, "Invalid reply to find command."); ++ ++ future_destroy(future); ++ request_destroy(request); ++ mongoc_cursor_destroy(cursor); ++ mongoc_collection_destroy(collection); ++ } ++ ++ // Test an omitted "ns" ++ { ++ mongoc_database_t *database = mongoc_client_get_database(client, "db"); ++ mongoc_cursor_t *cursor = mongoc_database_aggregate(database, tmp_bson("[]"), NULL, NULL); ++ ++ future_t *future = future_cursor_next(cursor, &doc); ++ request_t *request = mock_server_receives_msg(server, MONGOC_MSG_NONE, tmp_bson("{'aggregate': 1, '$db': 'db'}")); ++ reply_to_op_msg_request( ++ request, ++ MONGOC_MSG_NONE, ++ tmp_bson(BSON_STR({"ok" : 1, "cursor" : {"id" : {"$numberLong" : "1234"}, "firstBatch" : [ {} ]}}))); ++ ++ ASSERT(!future_get_bool(future)); ++ ASSERT(mongoc_cursor_error(cursor, &error)); ++ ASSERT_ERROR_CONTAINS( ++ error, MONGOC_ERROR_PROTOCOL, MONGOC_ERROR_PROTOCOL_INVALID_REPLY, "Invalid reply to aggregate command."); ++ ++ future_destroy(future); ++ request_destroy(request); ++ mongoc_cursor_destroy(cursor); ++ mongoc_database_destroy(database); ++ } ++ ++ // Test a trailing "." ++ { ++ mongoc_collection_t *collection = mongoc_client_get_collection(client, "db", "coll"); ++ mongoc_cursor_t *cursor = mongoc_collection_find_with_opts(collection, tmp_bson("{}"), NULL, NULL); ++ ++ future_t *future = future_cursor_next(cursor, &doc); ++ request_t *request = mock_server_receives_msg(server, MONGOC_MSG_NONE, tmp_bson("{'find': 'coll', '$db': 'db'}")); ++ reply_to_op_msg_request( ++ request, ++ MONGOC_MSG_NONE, ++ tmp_bson( ++ BSON_STR({"ok" : 1, "cursor" : {"id" : {"$numberLong" : "1234"}, "ns" : "db.", "firstBatch" : [ {} ]}}))); ++ ++ ASSERT(!future_get_bool(future)); ++ ASSERT(mongoc_cursor_error(cursor, &error)); ++ ASSERT_ERROR_CONTAINS( ++ error, MONGOC_ERROR_PROTOCOL, MONGOC_ERROR_PROTOCOL_INVALID_REPLY, "Invalid reply to find command."); ++ ++ future_destroy(future); ++ request_destroy(request); ++ mongoc_cursor_destroy(cursor); ++ mongoc_collection_destroy(collection); ++ } ++ ++ // Test clone after bad "ns" ++ { ++ mongoc_collection_t *collection = mongoc_client_get_collection(client, "db", "coll"); ++ mongoc_cursor_t *cursor = mongoc_collection_find_with_opts(collection, tmp_bson("{}"), NULL, NULL); ++ ++ future_t *future = future_cursor_next(cursor, &doc); ++ request_t *request = mock_server_receives_msg(server, MONGOC_MSG_NONE, tmp_bson("{'find': 'coll', '$db': 'db'}")); ++ reply_to_op_msg_request( ++ request, ++ MONGOC_MSG_NONE, ++ tmp_bson(BSON_STR({"ok" : 1, "cursor" : {"id" : {"$numberLong" : "0"}, "ns" : "db", "firstBatch" : [ {} ]}}))); ++ ++ ASSERT(future_get_bool(future)); ++ ASSERT_OR_PRINT(!mongoc_cursor_error(cursor, &error), error); ++ future_destroy(future); ++ request_destroy(request); ++ ++ // The cloned cursor uses the original valid namespace. ++ mongoc_cursor_t *clone = mongoc_cursor_clone(cursor); ++ future = future_cursor_next(clone, &doc); ++ request = mock_server_receives_msg(server, MONGOC_MSG_NONE, tmp_bson("{'find': 'coll', '$db': 'db'}")); ++ reply_to_op_msg_request( ++ request, ++ MONGOC_MSG_NONE, ++ tmp_bson( ++ BSON_STR({"ok" : 1, "cursor" : {"id" : {"$numberLong" : "0"}, "ns" : "db.coll", "firstBatch" : [ {} ]}}))); ++ ++ ASSERT(future_get_bool(future)); ++ ASSERT_OR_PRINT(!mongoc_cursor_error(clone, &error), error); ++ ++ future_destroy(future); ++ request_destroy(request); ++ mongoc_cursor_destroy(clone); ++ mongoc_cursor_destroy(cursor); ++ mongoc_collection_destroy(collection); ++ } ++ ++ mongoc_client_destroy(client); ++ mock_server_destroy(server); ++} ++ ++ + static void + test_error_document_query (void) + { +@@ -2620,6 +2744,7 @@ test_cursor_install (TestSuite *suite) + TestSuite_AddMockServerTest (suite, "/Cursor/n_return/find_cmd/with_opts", test_n_return_find_cmd_with_opts); + TestSuite_AddLive (suite, "/Cursor/empty_final_batch_live", test_empty_final_batch_live); + TestSuite_AddMockServerTest (suite, "/Cursor/empty_final_batch", test_empty_final_batch); ++ TestSuite_AddMockServerTest (suite, "/Cursor/bad_ns", test_cursor_bad_ns); + TestSuite_AddLive (suite, "/Cursor/error_document/query", test_error_document_query); + TestSuite_AddLive (suite, "/Cursor/error_document/getmore", test_error_document_getmore); + TestSuite_AddLive (suite, "/Cursor/error_document/command", test_error_document_command); +-- +2.39.5 + diff -Nru mongo-c-driver-1.30.4/debian/patches/0026_CVE-2026-120956.patch mongo-c-driver-1.30.4/debian/patches/0026_CVE-2026-120956.patch --- mongo-c-driver-1.30.4/debian/patches/0026_CVE-2026-120956.patch 1970-01-01 00:00:00.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/patches/0026_CVE-2026-120956.patch 2026-11-03 21:12:33.000000000 +0000 @@ -0,0 +1,457 @@ +From 4fdc5d553801fcde7bd5c86370917cc4ac704f54 Mon Sep 17 00:00:00 2001 +From: Sabrina Chang +Date: Mon, 2 Nov 2026 08:30:42 -0500 +Subject: [PATCH] CDRIVER-6501 reject an embedded NUL in a cursor reply + namespace + +--- + src/libmongoc/src/mongoc/mongoc-cursor.c | 20 +- + src/libmongoc/tests/test-mongoc-cursor.c | 323 ++++++++++++++++++----- + 2 files changed, 271 insertions(+), 72 deletions(-) + +diff --git a/src/libmongoc/src/mongoc/mongoc-cursor.c b/src/libmongoc/src/mongoc/mongoc-cursor.c +index c422d59d59..3bf5d8de18 100644 +--- a/src/libmongoc/src/mongoc/mongoc-cursor.c ++++ b/src/libmongoc/src/mongoc/mongoc-cursor.c +@@ -160,6 +160,10 @@ _mongoc_set_cursor_ns (mongoc_cursor_t *cursor, const char *ns, uint32_t nslen) + { + const char *dot; + ++ /* Precondition: `ns` must not contain an embedded NUL. Callers reject such namespaces; a NUL here would make ++ * `bson_strndup` copy less than `nslen`, leaving `cursor->nslen` larger than the buffer it describes. */ ++ BSON_ASSERT (memchr (ns, '\0', nslen) == NULL); ++ + bson_free (cursor->ns); + cursor->ns = bson_strndup (ns, nslen); + cursor->nslen = nslen; +@@ -1616,6 +1620,7 @@ _mongoc_cursor_start_reading_response (mongoc_cursor_t *cursor, mongoc_cursor_re + bson_iter_t child; + const char *ns = NULL; + uint32_t nslen; ++ bool ns_invalid_type = false; + bool in_batch = false; + + if (bson_iter_init_find (&iter, &response->reply, "cursor") && BSON_ITER_HOLDS_DOCUMENT (&iter) && +@@ -1624,7 +1629,12 @@ _mongoc_cursor_start_reading_response (mongoc_cursor_t *cursor, mongoc_cursor_re + if (BSON_ITER_IS_KEY (&child, "id")) { + cursor->cursor_id = bson_iter_as_int64 (&child); + } else if (BSON_ITER_IS_KEY (&child, "ns")) { +- ns = bson_iter_utf8 (&child, &nslen); ++ if (BSON_ITER_HOLDS_UTF8 (&child)) { ++ ns = bson_iter_utf8 (&child, &nslen); ++ } else { ++ /* ns not a string. Handle as malformed rather than absent. */ ++ ns_invalid_type = true; ++ } + } else if (BSON_ITER_IS_KEY (&child, "firstBatch") || BSON_ITER_IS_KEY (&child, "nextBatch")) { + if (BSON_ITER_HOLDS_ARRAY (&child) && bson_iter_recurse (&child, &response->batch_iter)) { + in_batch = true; +@@ -1647,13 +1657,19 @@ _mongoc_cursor_start_reading_response (mongoc_cursor_t *cursor, mongoc_cursor_re + * Note: a database aggregate cursor returns a namespace like ".$cmd.aggregate". */ + const char *const dot = memchr (ns, '.', nslen); + const bool has_collection = dot && (uint32_t) (dot - ns) + 1 < nslen; +- if (has_collection) { ++ /* A namespace can never contain an embedded NUL. Reject one rather than adopting it. */ ++ const bool has_embedded_nul = memchr (ns, '\0', nslen) != NULL; ++ if (has_collection && !has_embedded_nul) { + _mongoc_set_cursor_ns (cursor, ns, nslen); + } else if (cursor->cursor_id) { + /* Set 0 cursor ID to prevent sending killCursors. */ + cursor->cursor_id = 0; + return false; + } ++ } else if (ns_invalid_type && cursor->cursor_id) { ++ // "ns" was present but not a string: set 0 cursor ID to prevent sending killCursors. ++ cursor->cursor_id = 0; ++ return false; + } + + /* A non-zero cursor ID requires a collection to send "getMore" and "killCursors". */ +diff --git a/src/libmongoc/tests/test-mongoc-cursor.c b/src/libmongoc/tests/test-mongoc-cursor.c +index 9f95a6a8d2..fae24b2d68 100644 +--- a/src/libmongoc/tests/test-mongoc-cursor.c ++++ b/src/libmongoc/tests/test-mongoc-cursor.c +@@ -2109,127 +2109,310 @@ test_empty_final_batch (void) + } + + ++// Build a reply of the form {"ok":1,"cursor":{"id":,"ns":,"firstBatch":[{}]}}. `ns` is appended with an ++// explicit length so that it can contain an embedded NUL for CDRIVER-6501 ++static bson_t * ++cursor_reply_with_ns (int64_t cursor_id, const char *ns, int nslen) ++{ ++ bson_t *const reply = bson_new (); ++ bson_t cursor; ++ bson_t batch; ++ bson_t first; ++ ++ bson_append_document_begin (reply, "cursor", -1, &cursor); ++ bson_append_int64 (&cursor, "id", -1, cursor_id); ++ bson_append_utf8 (&cursor, "ns", -1, ns, nslen); ++ bson_append_array_begin (&cursor, "firstBatch", -1, &batch); ++ bson_append_document_begin (&batch, "0", -1, &first); ++ bson_append_document_end (&batch, &first); ++ bson_append_array_end (&cursor, &batch); ++ bson_append_document_end (reply, &cursor); ++ bson_append_int32 (reply, "ok", -1, 1); ++ ++ return reply; ++} ++ ++ + // Test server replies with an unusable cursor namespace. Regression test for CDRIVER-6401. + static void +-test_cursor_bad_ns(void) ++test_cursor_bad_ns (void) + { +- mock_server_t *server = mock_server_with_auto_hello(WIRE_VERSION_MIN); +- mock_server_run(server); ++ mock_server_t *server = mock_server_with_auto_hello (WIRE_VERSION_MIN); ++ mock_server_run (server); + +- mongoc_client_t *client = test_framework_client_new_from_uri(mock_server_get_uri(server), NULL); ++ mongoc_client_t *client = test_framework_client_new_from_uri (mock_server_get_uri (server), NULL); + const bson_t *doc; + bson_error_t error; + + // Test "ns" without "." + { +- mongoc_collection_t *collection = mongoc_client_get_collection(client, "db", "coll"); +- mongoc_cursor_t *cursor = mongoc_collection_find_with_opts(collection, tmp_bson("{}"), NULL, NULL); ++ mongoc_collection_t *collection = mongoc_client_get_collection (client, "db", "coll"); ++ mongoc_cursor_t *cursor = mongoc_collection_find_with_opts (collection, tmp_bson ("{}"), NULL, NULL); + +- future_t *future = future_cursor_next(cursor, &doc); +- request_t *request = mock_server_receives_msg(server, MONGOC_MSG_NONE, tmp_bson("{'find': 'coll', '$db': 'db'}")); +- reply_to_op_msg_request( ++ future_t *future = future_cursor_next (cursor, &doc); ++ request_t *request = ++ mock_server_receives_msg (server, MONGOC_MSG_NONE, tmp_bson ("{'find': 'coll', '$db': 'db'}")); ++ reply_to_op_msg_request ( + request, + MONGOC_MSG_NONE, +- tmp_bson( +- BSON_STR({"ok" : 1, "cursor" : {"id" : {"$numberLong" : "1234"}, "ns" : "db", "firstBatch" : [ {} ]}}))); ++ tmp_bson ( ++ BSON_STR ({"ok" : 1, "cursor" : {"id" : {"$numberLong" : "1234"}, "ns" : "db", "firstBatch" : [ {} ]}}))); + +- ASSERT(!future_get_bool(future)); +- ASSERT(mongoc_cursor_error(cursor, &error)); +- ASSERT_ERROR_CONTAINS( ++ ASSERT (!future_get_bool (future)); ++ ASSERT (mongoc_cursor_error (cursor, &error)); ++ ASSERT_ERROR_CONTAINS ( + error, MONGOC_ERROR_PROTOCOL, MONGOC_ERROR_PROTOCOL_INVALID_REPLY, "Invalid reply to find command."); + +- future_destroy(future); +- request_destroy(request); +- mongoc_cursor_destroy(cursor); +- mongoc_collection_destroy(collection); ++ future_destroy (future); ++ request_destroy (request); ++ mongoc_cursor_destroy (cursor); ++ mongoc_collection_destroy (collection); + } + + // Test an omitted "ns" + { +- mongoc_database_t *database = mongoc_client_get_database(client, "db"); +- mongoc_cursor_t *cursor = mongoc_database_aggregate(database, tmp_bson("[]"), NULL, NULL); ++ mongoc_database_t *database = mongoc_client_get_database (client, "db"); ++ mongoc_cursor_t *cursor = mongoc_database_aggregate (database, tmp_bson ("[]"), NULL, NULL); + +- future_t *future = future_cursor_next(cursor, &doc); +- request_t *request = mock_server_receives_msg(server, MONGOC_MSG_NONE, tmp_bson("{'aggregate': 1, '$db': 'db'}")); +- reply_to_op_msg_request( ++ future_t *future = future_cursor_next (cursor, &doc); ++ request_t *request = ++ mock_server_receives_msg (server, MONGOC_MSG_NONE, tmp_bson ("{'aggregate': 1, '$db': 'db'}")); ++ reply_to_op_msg_request ( + request, + MONGOC_MSG_NONE, +- tmp_bson(BSON_STR({"ok" : 1, "cursor" : {"id" : {"$numberLong" : "1234"}, "firstBatch" : [ {} ]}}))); ++ tmp_bson (BSON_STR ({"ok" : 1, "cursor" : {"id" : {"$numberLong" : "1234"}, "firstBatch" : [ {} ]}}))); + +- ASSERT(!future_get_bool(future)); +- ASSERT(mongoc_cursor_error(cursor, &error)); +- ASSERT_ERROR_CONTAINS( ++ ASSERT (!future_get_bool (future)); ++ ASSERT (mongoc_cursor_error (cursor, &error)); ++ ASSERT_ERROR_CONTAINS ( + error, MONGOC_ERROR_PROTOCOL, MONGOC_ERROR_PROTOCOL_INVALID_REPLY, "Invalid reply to aggregate command."); + +- future_destroy(future); +- request_destroy(request); +- mongoc_cursor_destroy(cursor); +- mongoc_database_destroy(database); ++ future_destroy (future); ++ request_destroy (request); ++ mongoc_cursor_destroy (cursor); ++ mongoc_database_destroy (database); + } + + // Test a trailing "." + { +- mongoc_collection_t *collection = mongoc_client_get_collection(client, "db", "coll"); +- mongoc_cursor_t *cursor = mongoc_collection_find_with_opts(collection, tmp_bson("{}"), NULL, NULL); ++ mongoc_collection_t *collection = mongoc_client_get_collection (client, "db", "coll"); ++ mongoc_cursor_t *cursor = mongoc_collection_find_with_opts (collection, tmp_bson ("{}"), NULL, NULL); ++ ++ future_t *future = future_cursor_next (cursor, &doc); ++ request_t *request = ++ mock_server_receives_msg (server, MONGOC_MSG_NONE, tmp_bson ("{'find': 'coll', '$db': 'db'}")); ++ reply_to_op_msg_request ( ++ request, ++ MONGOC_MSG_NONE, ++ tmp_bson ( ++ BSON_STR ({"ok" : 1, "cursor" : {"id" : {"$numberLong" : "1234"}, "ns" : "db.", "firstBatch" : [ {} ]}}))); ++ ++ ASSERT (!future_get_bool (future)); ++ ASSERT (mongoc_cursor_error (cursor, &error)); ++ ASSERT_ERROR_CONTAINS ( ++ error, MONGOC_ERROR_PROTOCOL, MONGOC_ERROR_PROTOCOL_INVALID_REPLY, "Invalid reply to find command."); ++ ++ future_destroy (future); ++ request_destroy (request); ++ mongoc_cursor_destroy (cursor); ++ mongoc_collection_destroy (collection); ++ } ++ ++ // Test an embedded NUL in the collection. ++ { ++ mongoc_collection_t *collection = mongoc_client_get_collection (client, "db", "coll"); ++ mongoc_cursor_t *cursor = mongoc_collection_find_with_opts (collection, tmp_bson ("{}"), NULL, NULL); ++ ++ future_t *future = future_cursor_next (cursor, &doc); ++ request_t *request = ++ mock_server_receives_msg (server, MONGOC_MSG_NONE, tmp_bson ("{'find': 'coll', '$db': 'db'}")); ++ bson_t *reply = cursor_reply_with_ns (1234, "db.coll\0x", 9); ++ reply_to_op_msg_request (request, MONGOC_MSG_NONE, reply); ++ bson_destroy (reply); ++ ++ ASSERT (!future_get_bool (future)); ++ ASSERT (mongoc_cursor_error (cursor, &error)); ++ ASSERT_ERROR_CONTAINS ( ++ error, MONGOC_ERROR_PROTOCOL, MONGOC_ERROR_PROTOCOL_INVALID_REPLY, "Invalid reply to find command."); ++ ++ future_destroy (future); ++ request_destroy (request); ++ mongoc_cursor_destroy (cursor); ++ mongoc_collection_destroy (collection); ++ } ++ ++ // Test an embedded NUL in the database portion. ++ { ++ mongoc_collection_t *collection = mongoc_client_get_collection (client, "db", "coll"); ++ mongoc_cursor_t *cursor = mongoc_collection_find_with_opts (collection, tmp_bson ("{}"), NULL, NULL); ++ ++ future_t *future = future_cursor_next (cursor, &doc); ++ request_t *request = ++ mock_server_receives_msg (server, MONGOC_MSG_NONE, tmp_bson ("{'find': 'coll', '$db': 'db'}")); ++ bson_t *reply = cursor_reply_with_ns (1234, "db\0x.coll", 9); ++ reply_to_op_msg_request (request, MONGOC_MSG_NONE, reply); ++ bson_destroy (reply); ++ ++ ASSERT (!future_get_bool (future)); ++ ASSERT (mongoc_cursor_error (cursor, &error)); ++ ASSERT_ERROR_CONTAINS ( ++ error, MONGOC_ERROR_PROTOCOL, MONGOC_ERROR_PROTOCOL_INVALID_REPLY, "Invalid reply to find command."); ++ ++ future_destroy (future); ++ request_destroy (request); ++ mongoc_cursor_destroy (cursor); ++ mongoc_collection_destroy (collection); ++ } ++ ++ // Test an embedded NUL with no "." separator. ++ { ++ mongoc_collection_t *collection = mongoc_client_get_collection (client, "db", "coll"); ++ mongoc_cursor_t *cursor = mongoc_collection_find_with_opts (collection, tmp_bson ("{}"), NULL, NULL); ++ ++ future_t *future = future_cursor_next (cursor, &doc); ++ request_t *request = ++ mock_server_receives_msg (server, MONGOC_MSG_NONE, tmp_bson ("{'find': 'coll', '$db': 'db'}")); ++ bson_t *reply = cursor_reply_with_ns (1234, "db\0x", 4); ++ reply_to_op_msg_request (request, MONGOC_MSG_NONE, reply); ++ bson_destroy (reply); ++ ++ ASSERT (!future_get_bool (future)); ++ ASSERT (mongoc_cursor_error (cursor, &error)); ++ ASSERT_ERROR_CONTAINS ( ++ error, MONGOC_ERROR_PROTOCOL, MONGOC_ERROR_PROTOCOL_INVALID_REPLY, "Invalid reply to find command."); ++ ++ future_destroy (future); ++ request_destroy (request); ++ mongoc_cursor_destroy (cursor); ++ mongoc_collection_destroy (collection); ++ } ++ ++ // Test an embedded NUL with an exhausted cursor. ++ { ++ mongoc_collection_t *collection = mongoc_client_get_collection (client, "db", "coll"); ++ mongoc_cursor_t *cursor = mongoc_collection_find_with_opts (collection, tmp_bson ("{}"), NULL, NULL); ++ ++ future_t *future = future_cursor_next (cursor, &doc); ++ request_t *request = ++ mock_server_receives_msg (server, MONGOC_MSG_NONE, tmp_bson ("{'find': 'coll', '$db': 'db'}")); ++ bson_t *reply = cursor_reply_with_ns (0, "db.coll\0x", 9); ++ reply_to_op_msg_request (request, MONGOC_MSG_NONE, reply); ++ bson_destroy (reply); ++ ++ ASSERT (future_get_bool (future)); ++ ASSERT_OR_PRINT (!mongoc_cursor_error (cursor, &error), error); ++ future_destroy (future); ++ request_destroy (request); ++ ++ // The cloned cursor uses the original valid namespace. ++ mongoc_cursor_t *clone = mongoc_cursor_clone (cursor); ++ future = future_cursor_next (clone, &doc); ++ request = mock_server_receives_msg (server, MONGOC_MSG_NONE, tmp_bson ("{'find': 'coll', '$db': 'db'}")); ++ reply_to_op_msg_request ( ++ request, ++ MONGOC_MSG_NONE, ++ tmp_bson ( ++ BSON_STR ({"ok" : 1, "cursor" : {"id" : {"$numberLong" : "0"}, "ns" : "db.coll", "firstBatch" : [ {} ]}}))); ++ ++ ASSERT (future_get_bool (future)); ++ ASSERT_OR_PRINT (!mongoc_cursor_error (clone, &error), error); ++ ++ future_destroy (future); ++ request_destroy (request); ++ mongoc_cursor_destroy (clone); ++ mongoc_cursor_destroy (cursor); ++ mongoc_collection_destroy (collection); ++ } ++ ++ // Test a wrong-typed "ns" (present, but not a string) with a live cursor errors. ++ { ++ mongoc_collection_t *collection = mongoc_client_get_collection (client, "db", "coll"); ++ mongoc_cursor_t *cursor = mongoc_collection_find_with_opts (collection, tmp_bson ("{}"), NULL, NULL); + +- future_t *future = future_cursor_next(cursor, &doc); +- request_t *request = mock_server_receives_msg(server, MONGOC_MSG_NONE, tmp_bson("{'find': 'coll', '$db': 'db'}")); +- reply_to_op_msg_request( ++ future_t *future = future_cursor_next (cursor, &doc); ++ request_t *request = ++ mock_server_receives_msg (server, MONGOC_MSG_NONE, tmp_bson ("{'find': 'coll', '$db': 'db'}")); ++ reply_to_op_msg_request ( + request, + MONGOC_MSG_NONE, +- tmp_bson( +- BSON_STR({"ok" : 1, "cursor" : {"id" : {"$numberLong" : "1234"}, "ns" : "db.", "firstBatch" : [ {} ]}}))); ++ tmp_bson ( ++ BSON_STR ({"ok" : 1, "cursor" : {"id" : {"$numberLong" : "1234"}, "ns" : 5, "firstBatch" : [ {} ]}}))); + +- ASSERT(!future_get_bool(future)); +- ASSERT(mongoc_cursor_error(cursor, &error)); +- ASSERT_ERROR_CONTAINS( ++ ASSERT (!future_get_bool (future)); ++ ASSERT (mongoc_cursor_error (cursor, &error)); ++ ASSERT_ERROR_CONTAINS ( + error, MONGOC_ERROR_PROTOCOL, MONGOC_ERROR_PROTOCOL_INVALID_REPLY, "Invalid reply to find command."); + +- future_destroy(future); +- request_destroy(request); +- mongoc_cursor_destroy(cursor); +- mongoc_collection_destroy(collection); ++ future_destroy (future); ++ request_destroy (request); ++ mongoc_cursor_destroy (cursor); ++ mongoc_collection_destroy (collection); ++ } ++ ++ // Test a wrong-typed "ns" with an exhausted cursor: no error is raised and the ++ // original namespace is preserved. ++ { ++ mongoc_collection_t *collection = mongoc_client_get_collection (client, "db", "coll"); ++ mongoc_cursor_t *cursor = mongoc_collection_find_with_opts (collection, tmp_bson ("{}"), NULL, NULL); ++ ++ future_t *future = future_cursor_next (cursor, &doc); ++ request_t *request = ++ mock_server_receives_msg (server, MONGOC_MSG_NONE, tmp_bson ("{'find': 'coll', '$db': 'db'}")); ++ reply_to_op_msg_request ( ++ request, ++ MONGOC_MSG_NONE, ++ tmp_bson (BSON_STR ({"ok" : 1, "cursor" : {"id" : {"$numberLong" : "0"}, "ns" : 5, "firstBatch" : [ {} ]}}))); ++ ++ ASSERT (future_get_bool (future)); ++ ASSERT_OR_PRINT (!mongoc_cursor_error (cursor, &error), error); ++ ++ future_destroy (future); ++ request_destroy (request); ++ mongoc_cursor_destroy (cursor); ++ mongoc_collection_destroy (collection); + } + + // Test clone after bad "ns" + { +- mongoc_collection_t *collection = mongoc_client_get_collection(client, "db", "coll"); +- mongoc_cursor_t *cursor = mongoc_collection_find_with_opts(collection, tmp_bson("{}"), NULL, NULL); ++ mongoc_collection_t *collection = mongoc_client_get_collection (client, "db", "coll"); ++ mongoc_cursor_t *cursor = mongoc_collection_find_with_opts (collection, tmp_bson ("{}"), NULL, NULL); + +- future_t *future = future_cursor_next(cursor, &doc); +- request_t *request = mock_server_receives_msg(server, MONGOC_MSG_NONE, tmp_bson("{'find': 'coll', '$db': 'db'}")); +- reply_to_op_msg_request( ++ future_t *future = future_cursor_next (cursor, &doc); ++ request_t *request = ++ mock_server_receives_msg (server, MONGOC_MSG_NONE, tmp_bson ("{'find': 'coll', '$db': 'db'}")); ++ reply_to_op_msg_request ( + request, + MONGOC_MSG_NONE, +- tmp_bson(BSON_STR({"ok" : 1, "cursor" : {"id" : {"$numberLong" : "0"}, "ns" : "db", "firstBatch" : [ {} ]}}))); ++ tmp_bson ( ++ BSON_STR ({"ok" : 1, "cursor" : {"id" : {"$numberLong" : "0"}, "ns" : "db", "firstBatch" : [ {} ]}}))); + +- ASSERT(future_get_bool(future)); +- ASSERT_OR_PRINT(!mongoc_cursor_error(cursor, &error), error); +- future_destroy(future); +- request_destroy(request); ++ ASSERT (future_get_bool (future)); ++ ASSERT_OR_PRINT (!mongoc_cursor_error (cursor, &error), error); ++ future_destroy (future); ++ request_destroy (request); + + // The cloned cursor uses the original valid namespace. +- mongoc_cursor_t *clone = mongoc_cursor_clone(cursor); +- future = future_cursor_next(clone, &doc); +- request = mock_server_receives_msg(server, MONGOC_MSG_NONE, tmp_bson("{'find': 'coll', '$db': 'db'}")); +- reply_to_op_msg_request( ++ mongoc_cursor_t *clone = mongoc_cursor_clone (cursor); ++ future = future_cursor_next (clone, &doc); ++ request = mock_server_receives_msg (server, MONGOC_MSG_NONE, tmp_bson ("{'find': 'coll', '$db': 'db'}")); ++ reply_to_op_msg_request ( + request, + MONGOC_MSG_NONE, +- tmp_bson( +- BSON_STR({"ok" : 1, "cursor" : {"id" : {"$numberLong" : "0"}, "ns" : "db.coll", "firstBatch" : [ {} ]}}))); ++ tmp_bson ( ++ BSON_STR ({"ok" : 1, "cursor" : {"id" : {"$numberLong" : "0"}, "ns" : "db.coll", "firstBatch" : [ {} ]}}))); + +- ASSERT(future_get_bool(future)); +- ASSERT_OR_PRINT(!mongoc_cursor_error(clone, &error), error); ++ ASSERT (future_get_bool (future)); ++ ASSERT_OR_PRINT (!mongoc_cursor_error (clone, &error), error); + +- future_destroy(future); +- request_destroy(request); +- mongoc_cursor_destroy(clone); +- mongoc_cursor_destroy(cursor); +- mongoc_collection_destroy(collection); ++ future_destroy (future); ++ request_destroy (request); ++ mongoc_cursor_destroy (clone); ++ mongoc_cursor_destroy (cursor); ++ mongoc_collection_destroy (collection); + } + +- mongoc_client_destroy(client); +- mock_server_destroy(server); ++ mongoc_client_destroy (client); ++ mock_server_destroy (server); + } + + +-- +2.39.5 + diff -Nru mongo-c-driver-1.30.4/debian/patches/0027_CDRIVER-6523.patch mongo-c-driver-1.30.4/debian/patches/0027_CDRIVER-6523.patch --- mongo-c-driver-1.30.4/debian/patches/0027_CDRIVER-6523.patch 1970-01-01 00:00:00.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/patches/0027_CDRIVER-6523.patch 2026-11-03 21:12:33.000000000 +0000 @@ -0,0 +1,2202 @@ +From 071ca15cd8534a71ebf912a3371f9946e27fc0bf Mon Sep 17 00:00:00 2001 +From: Kevin Albertson +Date: Tue, 3 Nov 2026 07:03:42 -0500 +Subject: [PATCH] CDRIVER-6523 report writeConcernError consistently + +--- + src/libmongoc/src/mongoc/mongoc-cursor.c | 20 +- + .../src/mongoc/mongoc-gridfs-bucket-file.c | 13 +- + .../src/mongoc/mongoc-gridfs-bucket.c | 17 +- + .../write-concern-errors/aggregate.json | 135 ++++++++ + .../write-concern-errors/bulkWrite.json | 74 ++++ + .../client-bulkWrite.json | 70 ++++ + .../client-side-encryption.json | 284 +++++++++++++++ + .../write-concern-errors/crud.json | 322 ++++++++++++++++++ + .../db-coll-management.json | 201 +++++++++++ + .../write-concern-errors/findAndModify.json | 156 +++++++++ + .../write-concern-errors/gridfs.json | 291 ++++++++++++++++ + .../index-management.json | 161 +++++++++ + .../write-concern-errors/mapReduce.json | 90 +++++ + src/libmongoc/tests/test-mongoc-aggregate.c | 81 ++++- + .../tests/test-mongoc-retryable-writes.c | 7 +- + src/libmongoc/tests/unified/operation.c | 17 +- + src/libmongoc/tests/unified/runner.c | 22 ++ + 17 files changed, 1939 insertions(+), 22 deletions(-) + create mode 100644 src/libmongoc/tests/json/read_write_concern/write-concern-errors/aggregate.json + create mode 100644 src/libmongoc/tests/json/read_write_concern/write-concern-errors/bulkWrite.json + create mode 100644 src/libmongoc/tests/json/read_write_concern/write-concern-errors/client-bulkWrite.json + create mode 100644 src/libmongoc/tests/json/read_write_concern/write-concern-errors/client-side-encryption.json + create mode 100644 src/libmongoc/tests/json/read_write_concern/write-concern-errors/crud.json + create mode 100644 src/libmongoc/tests/json/read_write_concern/write-concern-errors/db-coll-management.json + create mode 100644 src/libmongoc/tests/json/read_write_concern/write-concern-errors/findAndModify.json + create mode 100644 src/libmongoc/tests/json/read_write_concern/write-concern-errors/gridfs.json + create mode 100644 src/libmongoc/tests/json/read_write_concern/write-concern-errors/index-management.json + create mode 100644 src/libmongoc/tests/json/read_write_concern/write-concern-errors/mapReduce.json + +diff --git a/src/libmongoc/src/mongoc/mongoc-cursor.c b/src/libmongoc/src/mongoc/mongoc-cursor.c +index 3bf5d8de18..097088e7c9 100644 +--- a/src/libmongoc/src/mongoc/mongoc-cursor.c ++++ b/src/libmongoc/src/mongoc/mongoc-cursor.c +@@ -1105,15 +1105,20 @@ retry: + bson_copy_to (reply, &cursor->error_doc); + } + +- /* Read and Write Concern Spec: "Drivers SHOULD parse server replies for a +- * "writeConcernError" field and report the error only in command-specific +- * helper methods that take a separate write concern parameter or an options +- * parameter that may contain a write concern option. ++ /* Read and Write Concern Spec: "Drivers MUST parse server replies for a ++ * "writeConcernError" field and report the error in all driver methods that ++ * send a server command that may include a `WriteConcern`, excluding ++ * `abortTransaction` and generic command methods." + * +- * Only command helpers with names like "_with_write_concern" can create +- * cursors with a non-NULL write_concern field. ++ * Of the commands sent by cursors, only "aggregate" may include a write ++ * concern, whether passed in opts or inherited (an inherited write concern ++ * is applied to the command via cursor->write_concern above). Such an ++ * aggregate has $out or $merge, which _mongoc_aggregate marks with ++ * is_aggr_with_write_stage, so check the reply if it is set. Generic ++ * command methods (e.g. mongoc_cursor_new_from_command_reply_with_opts) ++ * create cursors without the flag set and are excluded per the spec. + */ +- if (ret && cursor->write_concern) { ++ if (ret && cursor->is_aggr_with_write_stage) { + ret = !_mongoc_parse_wc_err (reply, &cursor->error); + } + +@@ -1359,6 +1364,7 @@ mongoc_cursor_clone (const mongoc_cursor_t *cursor) + _clone->nslen = cursor->nslen; + _clone->dblen = cursor->dblen; + _clone->explicit_session = cursor->explicit_session; ++ _clone->is_aggr_with_write_stage = cursor->is_aggr_with_write_stage; + + if (cursor->read_prefs) { + _clone->read_prefs = mongoc_read_prefs_copy (cursor->read_prefs); +diff --git a/src/libmongoc/src/mongoc/mongoc-gridfs-bucket-file.c b/src/libmongoc/src/mongoc/mongoc-gridfs-bucket-file.c +index 5f3219a937..341f5abb74 100644 +--- a/src/libmongoc/src/mongoc/mongoc-gridfs-bucket-file.c ++++ b/src/libmongoc/src/mongoc/mongoc-gridfs-bucket-file.c +@@ -126,6 +126,9 @@ _mongoc_gridfs_bucket_create_indexes (mongoc_gridfs_bucket_t *bucket, bson_error + * + *-------------------------------------------------------------------------- + */ ++static bool ++_mongoc_gridfs_bucket_write_chunk (mongoc_gridfs_bucket_file_t *file) BSON_GNUC_WARN_UNUSED_RESULT; ++ + static bool + _mongoc_gridfs_bucket_write_chunk (mongoc_gridfs_bucket_file_t *file) + { +@@ -350,7 +353,10 @@ _mongoc_gridfs_bucket_file_writev (mongoc_gridfs_bucket_file_t *file, const mong + + if (file->in_buffer == chunk_size) { + /* Buffer is filled, write the chunk */ +- _mongoc_gridfs_bucket_write_chunk (file); ++ if (!_mongoc_gridfs_bucket_write_chunk (file)) { ++ /* Error is set on file. */ ++ return -1; ++ } + } + } + } +@@ -445,7 +451,10 @@ _mongoc_gridfs_bucket_file_save (mongoc_gridfs_bucket_file_t *file) + + if (file->in_buffer != 0) { + length += file->in_buffer; +- _mongoc_gridfs_bucket_write_chunk (file); ++ if (!_mongoc_gridfs_bucket_write_chunk (file)) { ++ /* Error is set on file. */ ++ return false; ++ } + } + + file->length = length; +diff --git a/src/libmongoc/src/mongoc/mongoc-gridfs-bucket.c b/src/libmongoc/src/mongoc/mongoc-gridfs-bucket.c +index 5da5420685..d7fdf164a5 100644 +--- a/src/libmongoc/src/mongoc/mongoc-gridfs-bucket.c ++++ b/src/libmongoc/src/mongoc/mongoc-gridfs-bucket.c +@@ -230,6 +230,8 @@ mongoc_gridfs_bucket_upload_from_stream_with_id (mongoc_gridfs_bucket_t *bucket, + ssize_t bytes_read; + ssize_t bytes_written; + char buf[512]; ++ /* `mongoc_gridfs_bucket_stream_error` asserts on a non-NULL error. */ ++ bson_error_t local_error; + + BSON_ASSERT (bucket); + BSON_ASSERT (file_id); +@@ -245,7 +247,7 @@ mongoc_gridfs_bucket_upload_from_stream_with_id (mongoc_gridfs_bucket_t *bucket, + while ((bytes_read = mongoc_stream_read (source, buf, 512, 1, 0)) > 0) { + bytes_written = mongoc_stream_write (upload_stream, buf, bytes_read, 0); + if (bytes_written < 0) { +- BSON_ASSERT (mongoc_gridfs_bucket_stream_error (upload_stream, error)); ++ BSON_ASSERT (mongoc_gridfs_bucket_stream_error (upload_stream, error ? error : &local_error)); + mongoc_gridfs_bucket_abort_upload (upload_stream); + mongoc_stream_destroy (upload_stream); + return false; +@@ -258,10 +260,19 @@ mongoc_gridfs_bucket_upload_from_stream_with_id (mongoc_gridfs_bucket_t *bucket, + error, MONGOC_ERROR_GRIDFS, MONGOC_ERROR_GRIDFS_BUCKET_STREAM, "Error occurred on the provided stream."); + mongoc_stream_destroy (upload_stream); + return false; +- } else { ++ } ++ ++ /* Explicitly call `mongoc_stream_close` to surface an error on final writes. */ ++ if (mongoc_stream_close (upload_stream) != 0) { ++ BSON_ASSERT (mongoc_gridfs_bucket_stream_error (upload_stream, error ? error : &local_error)); ++ /* Per the GridFS spec, do not attempt to clean up orphaned chunks on error. */ ++ /* `mongoc_stream_destroy` will not save the file since an error is set on it. */ + mongoc_stream_destroy (upload_stream); +- return true; ++ return false; + } ++ ++ mongoc_stream_destroy (upload_stream); ++ return true; + } + + bool +diff --git a/src/libmongoc/tests/json/read_write_concern/write-concern-errors/aggregate.json b/src/libmongoc/tests/json/read_write_concern/write-concern-errors/aggregate.json +new file mode 100644 +index 0000000000..b55beb932e +--- /dev/null ++++ b/src/libmongoc/tests/json/read_write_concern/write-concern-errors/aggregate.json +@@ -0,0 +1,135 @@ ++{ ++ "description": "write-concern-errors-aggregate", ++ "schemaVersion": "1.0", ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "useMultipleMongoses": false ++ } ++ }, ++ { ++ "database": { ++ "id": "database0", ++ "client": "client0", ++ "databaseName": "write-concern-errors-tests" ++ } ++ }, ++ { ++ "collection": { ++ "id": "collection0", ++ "database": "database0", ++ "collectionName": "coll" ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "collectionName": "coll", ++ "databaseName": "write-concern-errors-tests", ++ "documents": [] ++ } ++ ], ++ "tests": [ ++ { ++ "description": "aggregate with $out reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "aggregate" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "aggregate", ++ "object": "collection0", ++ "arguments": { ++ "pipeline": [ ++ { ++ "$match": { ++ "_id": { ++ "$gt": 1 ++ } ++ } ++ }, ++ { ++ "$out": "aggregate_out" ++ } ++ ] ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "aggregate with $merge reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "aggregate" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "aggregate", ++ "object": "collection0", ++ "arguments": { ++ "pipeline": [ ++ { ++ "$match": { ++ "_id": { ++ "$gt": 1 ++ } ++ } ++ }, ++ { ++ "$merge": { ++ "into": "aggregate_merge" ++ } ++ } ++ ] ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ } ++ ] ++} +diff --git a/src/libmongoc/tests/json/read_write_concern/write-concern-errors/bulkWrite.json b/src/libmongoc/tests/json/read_write_concern/write-concern-errors/bulkWrite.json +new file mode 100644 +index 0000000000..2aabb4bfb0 +--- /dev/null ++++ b/src/libmongoc/tests/json/read_write_concern/write-concern-errors/bulkWrite.json +@@ -0,0 +1,74 @@ ++{ ++ "description": "write-concern-errors-bulkWrite", ++ "schemaVersion": "1.0", ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "useMultipleMongoses": false ++ } ++ }, ++ { ++ "database": { ++ "id": "database0", ++ "client": "client0", ++ "databaseName": "write-concern-errors-tests" ++ } ++ }, ++ { ++ "collection": { ++ "id": "collection0", ++ "database": "database0", ++ "collectionName": "coll" ++ } ++ } ++ ], ++ "tests": [ ++ { ++ "description": "bulkWrite reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "insert" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "bulkWrite", ++ "object": "collection0", ++ "arguments": { ++ "requests": [ ++ { ++ "insertOne": { ++ "document": { ++ "x": 33 ++ } ++ } ++ } ++ ] ++ }, ++ "expectError": { ++ "isError": true ++ } ++ } ++ ] ++ } ++ ] ++} +diff --git a/src/libmongoc/tests/json/read_write_concern/write-concern-errors/client-bulkWrite.json b/src/libmongoc/tests/json/read_write_concern/write-concern-errors/client-bulkWrite.json +new file mode 100644 +index 0000000000..de8bfe7c95 +--- /dev/null ++++ b/src/libmongoc/tests/json/read_write_concern/write-concern-errors/client-bulkWrite.json +@@ -0,0 +1,70 @@ ++{ ++ "description": "write-concern-errors-client-bulkWrite", ++ "schemaVersion": "1.21", ++ "runOnRequirements": [ ++ { ++ "minServerVersion": "8.0" ++ } ++ ], ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "useMultipleMongoses": false ++ } ++ } ++ ], ++ "tests": [ ++ { ++ "description": "clientBulkWrite reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "bulkWrite" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "clientBulkWrite", ++ "object": "client0", ++ "arguments": { ++ "models": [ ++ { ++ "insertOne": { ++ "namespace": "write-concern-errors-tests.coll", ++ "document": { ++ "x": 33 ++ } ++ } ++ } ++ ] ++ }, ++ "expectError": { ++ "writeConcernErrors": [ ++ { ++ "code": 79 ++ } ++ ] ++ } ++ } ++ ] ++ } ++ ] ++} +diff --git a/src/libmongoc/tests/json/read_write_concern/write-concern-errors/client-side-encryption.json b/src/libmongoc/tests/json/read_write_concern/write-concern-errors/client-side-encryption.json +new file mode 100644 +index 0000000000..590f2f9411 +--- /dev/null ++++ b/src/libmongoc/tests/json/read_write_concern/write-concern-errors/client-side-encryption.json +@@ -0,0 +1,284 @@ ++{ ++ "description": "write-concern-errors-client-side-encryption", ++ "schemaVersion": "1.8", ++ "runOnRequirements": [ ++ { ++ "csfle": true ++ } ++ ], ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "useMultipleMongoses": false ++ } ++ }, ++ { ++ "clientEncryption": { ++ "id": "clientEncryption0", ++ "clientEncryptionOpts": { ++ "keyVaultClient": "client0", ++ "keyVaultNamespace": "keyvault.datakeys", ++ "kmsProviders": { ++ "local": { ++ "key": { ++ "$$placeholder": 1 ++ } ++ } ++ } ++ } ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "databaseName": "keyvault", ++ "collectionName": "datakeys", ++ "documents": [ ++ { ++ "_id": { ++ "$binary": { ++ "base64": "bG9jYWxrZXlsb2NhbGtleQ==", ++ "subType": "04" ++ } ++ }, ++ "keyAltNames": [ ++ "local_key" ++ ], ++ "keyMaterial": { ++ "$binary": { ++ "base64": "ABKBldDEoDW323yejOnIRk6YQmlD9d3eQthd16scKL75nz2LjNL9fgPDZWrFFOlqlhMCFaSrNJfGrFUjYk5JFDO7soG5Syb50k1niJoKg4ilsj0L4mpimFUtTpOr2nzZOeQtvAksEXc7gsFgq8gV7t/U3lsaXPY7I0t42DfSE8EGlPdxRjFdHnxh+OR8h7U9b8Qs5K5UuhgyeyxaBZ1Hgw==", ++ "subType": "00" ++ } ++ }, ++ "creationDate": { ++ "$date": { ++ "$numberLong": "1641024000000" ++ } ++ }, ++ "updateDate": { ++ "$date": { ++ "$numberLong": "1641024000000" ++ } ++ }, ++ "status": 1, ++ "masterKey": { ++ "provider": "local" ++ } ++ } ++ ] ++ } ++ ], ++ "tests": [ ++ { ++ "description": "rewrapManyDataKey reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "update" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "rewrapManyDataKey", ++ "object": "clientEncryption0", ++ "arguments": { ++ "filter": {}, ++ "opts": { ++ "provider": "local" ++ } ++ }, ++ "expectError": { ++ "isError": true ++ } ++ } ++ ] ++ }, ++ { ++ "description": "createDataKey reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "insert" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "createDataKey", ++ "object": "clientEncryption0", ++ "arguments": { ++ "kmsProvider": "local" ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "deleteKey reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "delete" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "deleteKey", ++ "object": "clientEncryption0", ++ "arguments": { ++ "id": { ++ "$binary": { ++ "base64": "bG9jYWxrZXlsb2NhbGtleQ==", ++ "subType": "04" ++ } ++ } ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "addKeyAltName reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "findAndModify" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "addKeyAltName", ++ "object": "clientEncryption0", ++ "arguments": { ++ "id": { ++ "$binary": { ++ "base64": "bG9jYWxrZXlsb2NhbGtleQ==", ++ "subType": "04" ++ } ++ }, ++ "keyAltName": "new_key_alt_name" ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "removeKeyAltName reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "findAndModify" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "removeKeyAltName", ++ "object": "clientEncryption0", ++ "arguments": { ++ "id": { ++ "$binary": { ++ "base64": "bG9jYWxrZXlsb2NhbGtleQ==", ++ "subType": "04" ++ } ++ }, ++ "keyAltName": "local_key" ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ } ++ ] ++} +diff --git a/src/libmongoc/tests/json/read_write_concern/write-concern-errors/crud.json b/src/libmongoc/tests/json/read_write_concern/write-concern-errors/crud.json +new file mode 100644 +index 0000000000..763f44023f +--- /dev/null ++++ b/src/libmongoc/tests/json/read_write_concern/write-concern-errors/crud.json +@@ -0,0 +1,322 @@ ++{ ++ "description": "write-concern-errors-crud", ++ "schemaVersion": "1.0", ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "useMultipleMongoses": false ++ } ++ }, ++ { ++ "database": { ++ "id": "database0", ++ "client": "client0", ++ "databaseName": "write-concern-errors-tests" ++ } ++ }, ++ { ++ "collection": { ++ "id": "collection0", ++ "database": "database0", ++ "collectionName": "coll" ++ } ++ } ++ ], ++ "tests": [ ++ { ++ "description": "insertOne reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "insert" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "insertOne", ++ "object": "collection0", ++ "arguments": { ++ "document": { ++ "x": 33 ++ } ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "insertMany reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "insert" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "insertMany", ++ "object": "collection0", ++ "arguments": { ++ "documents": [ ++ { ++ "x": 44 ++ }, ++ { ++ "x": 55 ++ } ++ ] ++ }, ++ "expectError": { ++ "isError": true ++ } ++ } ++ ] ++ }, ++ { ++ "description": "updateOne reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "update" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "updateOne", ++ "object": "collection0", ++ "arguments": { ++ "filter": { ++ "_id": 1 ++ }, ++ "update": { ++ "$set": { ++ "x": 111 ++ } ++ } ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "updateMany reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "update" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "updateMany", ++ "object": "collection0", ++ "arguments": { ++ "filter": {}, ++ "update": { ++ "$inc": { ++ "x": 1 ++ } ++ } ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "replaceOne reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "update" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "replaceOne", ++ "object": "collection0", ++ "arguments": { ++ "filter": { ++ "_id": 2 ++ }, ++ "replacement": { ++ "x": 222 ++ } ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "deleteOne reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "delete" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "deleteOne", ++ "object": "collection0", ++ "arguments": { ++ "filter": { ++ "_id": 1 ++ } ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "deleteMany reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "delete" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "deleteMany", ++ "object": "collection0", ++ "arguments": { ++ "filter": {} ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ } ++ ] ++} +diff --git a/src/libmongoc/tests/json/read_write_concern/write-concern-errors/db-coll-management.json b/src/libmongoc/tests/json/read_write_concern/write-concern-errors/db-coll-management.json +new file mode 100644 +index 0000000000..84f05d1936 +--- /dev/null ++++ b/src/libmongoc/tests/json/read_write_concern/write-concern-errors/db-coll-management.json +@@ -0,0 +1,201 @@ ++{ ++ "description": "write-concern-errors-db-coll-management", ++ "schemaVersion": "1.0", ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "useMultipleMongoses": false ++ } ++ }, ++ { ++ "database": { ++ "id": "database0", ++ "client": "client0", ++ "databaseName": "write-concern-errors-tests" ++ } ++ }, ++ { ++ "collection": { ++ "id": "collection0", ++ "database": "database0", ++ "collectionName": "coll" ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "collectionName": "coll", ++ "databaseName": "write-concern-errors-tests", ++ "documents": [] ++ } ++ ], ++ "tests": [ ++ { ++ "description": "createCollection reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "dropCollection", ++ "object": "database0", ++ "arguments": { ++ "collection": "coll_create" ++ } ++ }, ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "create" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "createCollection", ++ "object": "database0", ++ "arguments": { ++ "collection": "coll_create" ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "dropCollection reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "drop" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "dropCollection", ++ "object": "database0", ++ "arguments": { ++ "collection": "coll" ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "rename reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "dropCollection", ++ "object": "database0", ++ "arguments": { ++ "collection": "coll_renamed" ++ } ++ }, ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "renameCollection" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "rename", ++ "object": "collection0", ++ "arguments": { ++ "to": "coll_renamed" ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "dropDatabase reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "dropDatabase" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "dropDatabase", ++ "object": "client0", ++ "arguments": { ++ "database": "write-concern-errors-dropdb" ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ } ++ ] ++} +diff --git a/src/libmongoc/tests/json/read_write_concern/write-concern-errors/findAndModify.json b/src/libmongoc/tests/json/read_write_concern/write-concern-errors/findAndModify.json +new file mode 100644 +index 0000000000..05e55b836b +--- /dev/null ++++ b/src/libmongoc/tests/json/read_write_concern/write-concern-errors/findAndModify.json +@@ -0,0 +1,156 @@ ++{ ++ "description": "write-concern-errors-findAndModify", ++ "schemaVersion": "1.0", ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "useMultipleMongoses": false ++ } ++ }, ++ { ++ "database": { ++ "id": "database0", ++ "client": "client0", ++ "databaseName": "write-concern-errors-tests" ++ } ++ }, ++ { ++ "collection": { ++ "id": "collection0", ++ "database": "database0", ++ "collectionName": "coll" ++ } ++ } ++ ], ++ "tests": [ ++ { ++ "description": "findOneAndUpdate reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "findAndModify" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "findOneAndUpdate", ++ "object": "collection0", ++ "arguments": { ++ "filter": { ++ "_id": 1 ++ }, ++ "update": { ++ "$set": { ++ "x": 111 ++ } ++ } ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "findOneAndReplace reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "findAndModify" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "findOneAndReplace", ++ "object": "collection0", ++ "arguments": { ++ "filter": { ++ "_id": 2 ++ }, ++ "replacement": { ++ "x": 222 ++ } ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "findOneAndDelete reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "findAndModify" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "findOneAndDelete", ++ "object": "collection0", ++ "arguments": { ++ "filter": { ++ "_id": 1 ++ } ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ } ++ ] ++} +diff --git a/src/libmongoc/tests/json/read_write_concern/write-concern-errors/gridfs.json b/src/libmongoc/tests/json/read_write_concern/write-concern-errors/gridfs.json +new file mode 100644 +index 0000000000..bf753ee609 +--- /dev/null ++++ b/src/libmongoc/tests/json/read_write_concern/write-concern-errors/gridfs.json +@@ -0,0 +1,291 @@ ++{ ++ "description": "write-concern-errors-gridfs", ++ "schemaVersion": "1.0", ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "useMultipleMongoses": false ++ } ++ }, ++ { ++ "database": { ++ "id": "database0", ++ "client": "client0", ++ "databaseName": "write-concern-errors-tests" ++ } ++ }, ++ { ++ "bucket": { ++ "id": "bucket0", ++ "database": "database0" ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "databaseName": "write-concern-errors-tests", ++ "collectionName": "fs.files", ++ "documents": [ ++ { ++ "_id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "length": 0, ++ "chunkSize": 4, ++ "uploadDate": { ++ "$date": "1970-01-01T00:00:00.000Z" ++ }, ++ "filename": "filename" ++ } ++ ] ++ }, ++ { ++ "databaseName": "write-concern-errors-tests", ++ "collectionName": "fs.chunks", ++ "documents": [] ++ } ++ ], ++ "tests": [ ++ { ++ "description": "upload reports a writeConcernError from a chunk insert", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "insert" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "upload", ++ "object": "bucket0", ++ "arguments": { ++ "filename": "filename", ++ "source": { ++ "$$hexBytes": "1122334455" ++ } ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "upload reports a writeConcernError from the files document insert", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "skip": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "insert" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "upload", ++ "object": "bucket0", ++ "arguments": { ++ "filename": "filename", ++ "source": { ++ "$$hexBytes": "1122334455" ++ } ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "delete reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "delete" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "delete", ++ "object": "bucket0", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ } ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "deleteByName reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "delete" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "deleteByName", ++ "object": "bucket0", ++ "arguments": { ++ "filename": "filename" ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "file rename reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "update" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "rename", ++ "object": "bucket0", ++ "arguments": { ++ "id": { ++ "$oid": "000000000000000000000001" ++ }, ++ "newFilename": "newfilename" ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "renameByName reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "update" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "renameByName", ++ "object": "bucket0", ++ "arguments": { ++ "filename": "filename", ++ "newFilename": "newfilename" ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ } ++ ] ++} +diff --git a/src/libmongoc/tests/json/read_write_concern/write-concern-errors/index-management.json b/src/libmongoc/tests/json/read_write_concern/write-concern-errors/index-management.json +new file mode 100644 +index 0000000000..61aafe1d4f +--- /dev/null ++++ b/src/libmongoc/tests/json/read_write_concern/write-concern-errors/index-management.json +@@ -0,0 +1,161 @@ ++{ ++ "description": "write-concern-errors-index-management", ++ "schemaVersion": "1.0", ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "useMultipleMongoses": false ++ } ++ }, ++ { ++ "database": { ++ "id": "database0", ++ "client": "client0", ++ "databaseName": "write-concern-errors-tests" ++ } ++ }, ++ { ++ "collection": { ++ "id": "collection0", ++ "database": "database0", ++ "collectionName": "coll" ++ } ++ } ++ ], ++ "tests": [ ++ { ++ "description": "createIndex reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "createIndexes" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "createIndex", ++ "object": "collection0", ++ "arguments": { ++ "keys": { ++ "x": 1 ++ } ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "dropIndex reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "createIndex", ++ "object": "collection0", ++ "arguments": { ++ "keys": { ++ "x": 1 ++ }, ++ "name": "x_1" ++ } ++ }, ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "dropIndexes" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "dropIndex", ++ "object": "collection0", ++ "arguments": { ++ "name": "x_1" ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ }, ++ { ++ "description": "dropIndexes reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "createIndex", ++ "object": "collection0", ++ "arguments": { ++ "keys": { ++ "x": 1 ++ }, ++ "name": "x_1" ++ } ++ }, ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "dropIndexes" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "dropIndexes", ++ "object": "collection0", ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ } ++ ] ++} +diff --git a/src/libmongoc/tests/json/read_write_concern/write-concern-errors/mapReduce.json b/src/libmongoc/tests/json/read_write_concern/write-concern-errors/mapReduce.json +new file mode 100644 +index 0000000000..ce5979fabe +--- /dev/null ++++ b/src/libmongoc/tests/json/read_write_concern/write-concern-errors/mapReduce.json +@@ -0,0 +1,90 @@ ++{ ++ "description": "write-concern-errors-mapReduce", ++ "schemaVersion": "1.0", ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "useMultipleMongoses": false ++ } ++ }, ++ { ++ "database": { ++ "id": "database0", ++ "client": "client0", ++ "databaseName": "write-concern-errors-tests" ++ } ++ }, ++ { ++ "collection": { ++ "id": "collection0", ++ "database": "database0", ++ "collectionName": "coll" ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "collectionName": "coll", ++ "databaseName": "write-concern-errors-tests", ++ "documents": [ ++ { ++ "_id": 1, ++ "x": 11 ++ }, ++ { ++ "_id": 2, ++ "x": 22 ++ } ++ ] ++ } ++ ], ++ "tests": [ ++ { ++ "description": "mapReduce with an output collection reports a writeConcernError", ++ "operations": [ ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "mapReduce" ++ ], ++ "writeConcernError": { ++ "code": 79, ++ "codeName": "UnknownReplWriteConcern", ++ "errmsg": "Triggered by failpoint" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "name": "mapReduce", ++ "object": "collection0", ++ "arguments": { ++ "map": { ++ "$code": "function inc() { return emit(0, this.x + 1) }" ++ }, ++ "reduce": { ++ "$code": "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" ++ }, ++ "out": { ++ "replace": "mapreduce_out" ++ } ++ }, ++ "expectError": { ++ "errorCode": 79 ++ } ++ } ++ ] ++ } ++ ] ++} +diff --git a/src/libmongoc/tests/test-mongoc-aggregate.c b/src/libmongoc/tests/test-mongoc-aggregate.c +index c074c9c637..5fe510e404 100644 +--- a/src/libmongoc/tests/test-mongoc-aggregate.c ++++ b/src/libmongoc/tests/test-mongoc-aggregate.c +@@ -169,12 +169,91 @@ test_write_respects_read_prefs (void *unused) + bson_mutex_destroy (&lc.lock); + } + ++// `test_write_concern_error_after_clone` tests that an aggregate with a write ++// stage checks its reply for a "writeConcernError" after a call to ++// mongoc_cursor_clone. The check in _mongoc_cursor_run_command is gated on ++// cursor->is_aggr_with_write_stage, which mongoc_cursor_clone must copy to the ++// clone. This is a regression test for CDRIVER-6523. ++static void ++test_write_concern_error_after_clone (void *unused) ++{ ++ BSON_UNUSED (unused); ++ ++ mongoc_client_t *client = test_framework_new_default_client (); ++ ASSERT (mongoc_client_set_error_api (client, 2)); ++ mongoc_collection_t *coll = get_test_collection (client, "test_aggregate_wce_after_clone"); ++ ++ // Create the collection. On sharded clusters, an "aggregate" against a non-existent ++ // database fails with "database test not found" before the failpoint can reply with ++ // a writeConcernError. ++ { ++ bson_error_t error; ++ ASSERT_OR_PRINT (mongoc_collection_insert_one (coll, tmp_bson (BSON_STR ({"x" : 1})), NULL, NULL, &error), error); ++ } ++ ++ // Configure a failpoint to reply to "aggregate" with a writeConcernError, like the ++ // write-concern-errors spec tests: ++ { ++ bson_error_t error; ++ bson_t reply; ++ const char *cmd_str = BSON_STR ({ ++ "configureFailPoint" : "failCommand", ++ "mode" : {"times" : 1}, ++ "data" : { ++ "failCommands" : ["aggregate"], ++ "writeConcernError" : {"code" : 100, "errmsg" : "Not enough data-bearing nodes"} ++ } ++ }); ++ bson_t *failpoint_cmd = bson_new_from_json ((const uint8_t *) cmd_str, -1, &error); ++ ASSERT_OR_PRINT (failpoint_cmd, error); ++ ASSERT_OR_PRINT ( ++ mongoc_client_command_simple (client, "admin", failpoint_cmd, NULL /* read_prefs */, &reply, &error), error); ++ bson_destroy (failpoint_cmd); ++ bson_destroy (&reply); ++ } ++ ++ // Aggregate with $out and an explicit write concern in opts, so the cursor has no ++ // write_concern of its own and the reply check depends on is_aggr_with_write_stage. ++ // The cursor is cloned before iteration; only the clone's aggregate is sent. ++ { ++ bson_error_t error; ++ bson_t opts = BSON_INITIALIZER; ++ mongoc_write_concern_t *wc = mongoc_write_concern_new (); ++ mongoc_write_concern_set_wtag (wc, "majority"); ++ mongoc_write_concern_append (wc, &opts); ++ ++ mongoc_cursor_t *cursor = mongoc_collection_aggregate ( ++ coll, MONGOC_QUERY_NONE, tmp_bson (BSON_STR ({"pipeline" : [ {"$out" : "aggregate_out"} ]})), &opts, NULL); ++ mongoc_cursor_t *clone = mongoc_cursor_clone (cursor); ++ ++ const bson_t *doc; ++ ASSERT (!mongoc_cursor_next (clone, &doc)); ++ ASSERT (mongoc_cursor_error (clone, &error)); ++ ASSERT_ERROR_CONTAINS (error, MONGOC_ERROR_WRITE_CONCERN, 100, "Not enough data-bearing nodes"); ++ ++ mongoc_cursor_destroy (clone); ++ mongoc_cursor_destroy (cursor); ++ bson_destroy (&opts); ++ mongoc_write_concern_destroy (wc); ++ } ++ ++ mongoc_collection_destroy (coll); ++ mongoc_client_destroy (client); ++} ++ + void + test_aggregate_install (TestSuite *suite) + { + TestSuite_AddMockServerTest (suite, "/Aggregate/query_flags", test_query_flags); + TestSuite_AddFull (suite, +- "/Aggregate/write_respects_read_prefs", ++ "/Aggregate/write_concern_error_after_clone", ++ test_write_concern_error_after_clone, ++ NULL /* _dtor */, ++ NULL /* _ctx */, ++ /* requires failpoint */ ++ test_framework_skip_if_no_failpoint); ++ TestSuite_AddFull (suite, ++ "/Aggregate/write_respects_read_prefs [lock:live-server]", + test_write_respects_read_prefs, + NULL, + NULL, +diff --git a/src/libmongoc/tests/test-mongoc-retryable-writes.c b/src/libmongoc/tests/test-mongoc-retryable-writes.c +index 1c5170417f..b4a6e847f9 100644 +--- a/src/libmongoc/tests/test-mongoc-retryable-writes.c ++++ b/src/libmongoc/tests/test-mongoc-retryable-writes.c +@@ -601,7 +601,8 @@ set_up_original_error_test (mongoc_apm_callbacks_t *callbacks, + "admin", + tmp_bson ("{'configureFailPoint': 'failCommand', 'mode': {'times': 1}, " + "'data': {'failCommands': ['%s'], 'errorLabels': " +- "['RetryableWriteError'], 'writeConcernError': {'code': 91 }}}", ++ "['RetryableWriteError'], 'writeConcernError': {'code': 91, " ++ "'errmsg': 'Triggered by failpoint' }}}", + failCommand), + NULL, + NULL, +@@ -650,8 +651,8 @@ retryable_writes_prose_test_3 (void *ctx) + // attempt an insertOne operation + ASSERT (!mongoc_collection_insert_one (coll, tmp_bson ("{'x': 1}"), NULL /* opts */, &reply, &error)); + +- // writeConcernErrors are returned in the reply and not as an error +- ASSERT_ERROR_CONTAINS (error, 0, 0, ""); ++ // assert writeConcernError is reported. ++ ASSERT_ERROR_CONTAINS (error, MONGOC_ERROR_WRITE_CONCERN, 91, ""); + + // the reply holds the original error information + ASSERT_MATCH (&reply, +diff --git a/src/libmongoc/tests/unified/operation.c b/src/libmongoc/tests/unified/operation.c +index 8de42ddbee..e49253a2fc 100644 +--- a/src/libmongoc/tests/unified/operation.c ++++ b/src/libmongoc/tests/unified/operation.c +@@ -2718,7 +2718,12 @@ operation_upload (test_t *test, operation_t *op, result_t *result, bson_error_t + } + total_written += (size_t) bytes_written; + } +- mongoc_gridfs_bucket_stream_error (stream, &op_error); ++ /* Close the upload stream writes the final partial chunk and the files document. */ ++ /* Close is called in mongoc_stream_destroy. Close explicitly here to capture errors. */ ++ if (mongoc_stream_close (stream) != 0) { ++ mongoc_gridfs_bucket_stream_error (stream, &op_error); ++ mongoc_gridfs_bucket_abort_upload (stream); ++ } + bson_free (source_bytes); + } + +@@ -3610,12 +3615,12 @@ operation_rename (test_t *test, operation_t *op, result_t *result, bson_error_t + goto done; + } + +- // Rename the collection in the server, ++ // Rename the collection in the server. Record the error (if any) in the ++ // result instead of failing the operation, so "expectError" applies. + mongoc_collection_t *coll = ent->value; +- if (!mongoc_collection_rename (coll, NULL, new_name, drop_target ? *drop_target : false, error)) { +- goto done; +- } +- result_from_ok (result); ++ bson_error_t op_error = {0}; ++ mongoc_collection_rename (coll, NULL, new_name, drop_target ? *drop_target : false, &op_error); ++ result_from_val_and_reply (result, NULL, NULL, &op_error); + ret = true; + done: + bson_free (new_name); +diff --git a/src/libmongoc/tests/unified/runner.c b/src/libmongoc/tests/unified/runner.c +index 1d30f1cd7a..ddcd9838cb 100644 +--- a/src/libmongoc/tests/unified/runner.c ++++ b/src/libmongoc/tests/unified/runner.c +@@ -115,6 +115,26 @@ skipped_unified_test_t SKIPPED_TESTS[] = { + // libmongoc does not include insertId in InsertOneResult + {"cancel-server-check", SKIP_ALL_TESTS}, + ++ // libmongoc does not have a mapReduce helper. ++ {"write-concern-errors-mapReduce", SKIP_ALL_TESTS}, ++ ++ // libmongoc does not support the optional dropIndexes helper. ++ {"write-concern-errors-index-management", "dropIndexes reports a writeConcernError"}, ++ ++ // libmongoc does not have GridFS bucket deleteByName/rename/renameByName helpers. ++ {"write-concern-errors-gridfs", "deleteByName reports a writeConcernError"}, ++ {"write-concern-errors-gridfs", "file rename reports a writeConcernError"}, ++ {"write-concern-errors-gridfs", "renameByName reports a writeConcernError"}, ++ ++ // r1.30: the index-management paths (`createIndexes`, `dropIndex`) do not ++ // report a writeConcernError. The master rewrite that adds this is not ++ // backported. ++ {"write-concern-errors-index-management", "createIndex reports a writeConcernError"}, ++ {"write-concern-errors-index-management", "dropIndex reports a writeConcernError"}, ++ ++ // r1.30: the unified test runner does not have a dropDatabase operation. ++ {"write-concern-errors-db-coll-management", "dropDatabase reports a writeConcernError"}, ++ + {0}, + }; + // clang-format on +@@ -2214,6 +2234,8 @@ run_unified_tests (TestSuite *suite, const char *base, const char *subdir) + void + test_install_unified (TestSuite *suite) + { ++ run_unified_tests (suite, JSON_DIR, "read_write_concern/write-concern-errors"); ++ + run_unified_tests (suite, JSON_DIR, "unified"); + + run_unified_tests (suite, JSON_DIR, "gridfs/unified"); +-- +2.39.5 + diff -Nru mongo-c-driver-1.30.4/debian/patches/series mongo-c-driver-1.30.4/debian/patches/series --- mongo-c-driver-1.30.4/debian/patches/series 2026-08-29 15:06:27.000000000 +0000 +++ mongo-c-driver-1.30.4/debian/patches/series 2026-11-03 21:12:33.000000000 +0000 @@ -8,3 +8,21 @@ 0007_CVE-2025-14911.patch 0008_CVE-2026-6691.patch 0009_CVE-2026-81524.patch +0010_CVE-2026-84965.patch +0011_CVE-2026-84963.patch +0012_CVE-2026-84964.patch +0013_CVE-2026-84969.patch +0014_CVE-2026-88035.patch +0015_CVE-2026-88036.patch +0016_CVE-2026-93394.patch +0017_CVE-2026-96746.patch +0018_CVE-2026-106428.patch +0019_CVE-2026-106431.patch +0020_CVE-2026-106438.patch +0021_CVE-2026-106437.patch +0022_CVE-2026-120955.patch +0023_CVE-2026-120678.patch +0024_CVE-2026-120684.patch +0025_CDRIVER-6401.patch +0026_CVE-2026-120956.patch +0027_CDRIVER-6523.patch