mongo 2.24.1 → 2.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/mongo_console +2 -2
- data/lib/mongo/address/validator.rb +1 -1
- data/lib/mongo/address.rb +1 -3
- data/lib/mongo/auth/aws/conversation.rb +0 -4
- data/lib/mongo/auth/aws/credentials_retriever.rb +3 -8
- data/lib/mongo/auth/base.rb +1 -1
- data/lib/mongo/auth/sasl_conversation_base.rb +1 -1
- data/lib/mongo/auth/scram_conversation_base.rb +1 -8
- data/lib/mongo/auth/stringprep.rb +1 -6
- data/lib/mongo/auth/user/view.rb +1 -1
- data/lib/mongo/bulk_write/combineable.rb +0 -7
- data/lib/mongo/bulk_write/result_combiner.rb +3 -3
- data/lib/mongo/bulk_write/validatable.rb +0 -4
- data/lib/mongo/bulk_write.rb +1 -35
- data/lib/mongo/client.rb +117 -17
- data/lib/mongo/client_encryption.rb +36 -11
- data/lib/mongo/cluster/reapers/cursor_reaper.rb +12 -5
- data/lib/mongo/cluster/sdam_flow.rb +4 -4
- data/lib/mongo/cluster/topology/base.rb +1 -1
- data/lib/mongo/cluster.rb +13 -30
- data/lib/mongo/collection/queryable_encryption.rb +7 -6
- data/lib/mongo/collection/view/aggregation/behavior.rb +1 -1
- data/lib/mongo/collection/view/aggregation.rb +2 -4
- data/lib/mongo/collection/view/builder/aggregation.rb +3 -5
- data/lib/mongo/collection/view/builder/map_reduce.rb +1 -1
- data/lib/mongo/collection/view/change_stream.rb +14 -9
- data/lib/mongo/collection/view/map_reduce.rb +2 -11
- data/lib/mongo/collection/view/readable.rb +11 -7
- data/lib/mongo/collection/view/writable.rb +4 -39
- data/lib/mongo/collection/view.rb +8 -4
- data/lib/mongo/collection.rb +1 -1
- data/lib/mongo/crypt/auto_encrypter.rb +6 -2
- data/lib/mongo/crypt/binary.rb +7 -4
- data/lib/mongo/crypt/binding.rb +154 -11
- data/lib/mongo/crypt/context.rb +50 -19
- data/lib/mongo/crypt/encryption_io.rb +11 -5
- data/lib/mongo/crypt/explicit_encrypter.rb +27 -12
- data/lib/mongo/crypt/explicit_encryption_context.rb +51 -12
- data/lib/mongo/crypt/handle.rb +35 -5
- data/lib/mongo/crypt/kms/credentials.rb +70 -31
- data/lib/mongo/crypt/kms/kmip/master_document.rb +30 -1
- data/lib/mongo/crypt/kms/master_key_document.rb +11 -6
- data/lib/mongo/crypt/kms.rb +12 -1
- data/lib/mongo/cursor.rb +8 -2
- data/lib/mongo/database/cursor_command_view.rb +95 -0
- data/lib/mongo/database.rb +144 -18
- data/lib/mongo/error/invalid_uri.rb +5 -1
- data/lib/mongo/error/operation_failure.rb +2 -2
- data/lib/mongo/error/parser.rb +3 -3
- data/lib/mongo/error/sdam_error_detection.rb +2 -3
- data/lib/mongo/error/unsupported_option.rb +0 -40
- data/lib/mongo/grid/fs_bucket.rb +29 -23
- data/lib/mongo/grid/stream/read.rb +2 -2
- data/lib/mongo/grid/stream/write.rb +3 -3
- data/lib/mongo/index/view.rb +13 -7
- data/lib/mongo/monitoring/event/cmap/connection_check_out_failed.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_check_out_started.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_checked_in.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_checked_out.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_closed.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_created.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_ready.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/pool_cleared.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/pool_closed.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/pool_created.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/pool_ready.rb +1 -0
- data/lib/mongo/monitoring/event/command_failed.rb +1 -0
- data/lib/mongo/monitoring/event/command_started.rb +1 -0
- data/lib/mongo/monitoring/event/command_succeeded.rb +1 -0
- data/lib/mongo/monitoring/event/secure.rb +12 -17
- data/lib/mongo/monitoring/event/server_closed.rb +1 -0
- data/lib/mongo/monitoring/event/server_description_changed.rb +1 -0
- data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +1 -0
- data/lib/mongo/monitoring/event/server_heartbeat_started.rb +1 -0
- data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +1 -0
- data/lib/mongo/monitoring/event/server_opening.rb +1 -0
- data/lib/mongo/monitoring/event/topology_changed.rb +1 -0
- data/lib/mongo/monitoring/event/topology_closed.rb +1 -0
- data/lib/mongo/monitoring/event/topology_opening.rb +1 -0
- data/lib/mongo/operation/create/op_msg.rb +1 -0
- data/lib/mongo/operation/create_index/op_msg.rb +2 -1
- data/lib/mongo/operation/cursor_command/op_msg.rb +37 -0
- data/lib/mongo/operation/cursor_command/result.rb +60 -0
- data/lib/mongo/operation/cursor_command.rb +33 -0
- data/lib/mongo/operation/delete/op_msg.rb +1 -0
- data/lib/mongo/operation/drop/op_msg.rb +1 -0
- data/lib/mongo/operation/drop_database/op_msg.rb +1 -0
- data/lib/mongo/operation/drop_index/op_msg.rb +1 -0
- data/lib/mongo/operation/find/builder/command.rb +1 -1
- data/lib/mongo/operation/insert/bulk_result.rb +2 -4
- data/lib/mongo/operation/insert/op_msg.rb +1 -0
- data/lib/mongo/operation/result.rb +21 -25
- data/lib/mongo/operation/shared/causal_consistency_supported.rb +4 -2
- data/lib/mongo/operation/shared/executable.rb +2 -15
- data/lib/mongo/operation/shared/result/aggregatable.rb +2 -2
- data/lib/mongo/operation/shared/sessions_supported.rb +3 -5
- data/lib/mongo/operation/update/bulk_result.rb +1 -1
- data/lib/mongo/operation/update/op_msg.rb +1 -0
- data/lib/mongo/operation/write_command/op_msg.rb +2 -0
- data/lib/mongo/operation.rb +1 -0
- data/lib/mongo/options/redacted.rb +2 -2
- data/lib/mongo/protocol/compressed.rb +15 -11
- data/lib/mongo/protocol/message.rb +16 -20
- data/lib/mongo/protocol/msg.rb +1 -12
- data/lib/mongo/protocol/registry.rb +1 -1
- data/lib/mongo/protocol/serializers.rb +1 -3
- data/lib/mongo/query_cache.rb +3 -5
- data/lib/mongo/retryable/backpressure.rb +17 -2
- data/lib/mongo/retryable/read_worker.rb +1 -1
- data/lib/mongo/retryable/retry_policy.rb +2 -2
- data/lib/mongo/retryable/write_worker.rb +2 -2
- data/lib/mongo/retryable.rb +1 -1
- data/lib/mongo/server/app_metadata.rb +1 -1
- data/lib/mongo/server/connection.rb +3 -1
- data/lib/mongo/server/connection_common.rb +9 -1
- data/lib/mongo/server/connection_pool/generation_manager.rb +1 -1
- data/lib/mongo/server/connection_pool.rb +24 -10
- data/lib/mongo/server/description/features.rb +1 -2
- data/lib/mongo/server/description.rb +1 -2
- data/lib/mongo/server/monitor/connection.rb +3 -2
- data/lib/mongo/server/monitor.rb +133 -30
- data/lib/mongo/server/pending_connection.rb +1 -5
- data/lib/mongo/server/push_monitor.rb +16 -2
- data/lib/mongo/server.rb +28 -9
- data/lib/mongo/server_selector/base.rb +3 -6
- data/lib/mongo/server_selector/secondary_preferred.rb +4 -1
- data/lib/mongo/session.rb +25 -26
- data/lib/mongo/socket/ssl.rb +3 -3
- data/lib/mongo/socket.rb +1 -7
- data/lib/mongo/srv/monitor.rb +11 -2
- data/lib/mongo/srv/resolver.rb +1 -1
- data/lib/mongo/srv/result.rb +5 -3
- data/lib/mongo/timeout.rb +10 -15
- data/lib/mongo/tracing/open_telemetry/command_tracer.rb +16 -2
- data/lib/mongo/uri/options_mapper.rb +36 -13
- data/lib/mongo/uri/srv_protocol.rb +4 -4
- data/lib/mongo/uri.rb +56 -8
- data/lib/mongo/utils.rb +1 -1
- data/lib/mongo/version.rb +1 -1
- metadata +6 -4
- data/lib/mongo/auth/stringprep/unicode_normalize/normalize.rb +0 -173
- data/lib/mongo/auth/stringprep/unicode_normalize/tables.rb +0 -1164
data/lib/mongo/crypt/binding.rb
CHANGED
|
@@ -81,7 +81,7 @@ module Mongo
|
|
|
81
81
|
# will cause a `LoadError`.
|
|
82
82
|
#
|
|
83
83
|
# @api private
|
|
84
|
-
MIN_LIBMONGOCRYPT_VERSION = Gem::Version.new('1.
|
|
84
|
+
MIN_LIBMONGOCRYPT_VERSION = Gem::Version.new('1.20.0')
|
|
85
85
|
|
|
86
86
|
# @!method self.mongocrypt_version(len)
|
|
87
87
|
# @api private
|
|
@@ -106,7 +106,7 @@ module Mongo
|
|
|
106
106
|
def self.parse_version(version)
|
|
107
107
|
Gem::Version.new(version)
|
|
108
108
|
rescue ArgumentError
|
|
109
|
-
match = version.match(/\A(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)?(
|
|
109
|
+
match = version.match(/\A(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)?(?:-[A-Za-z+\d]+)?\z/)
|
|
110
110
|
raise ArgumentError.new("Malformed version number string #{version}") if match.nil?
|
|
111
111
|
|
|
112
112
|
Gem::Version.new(
|
|
@@ -823,14 +823,15 @@ module Mongo
|
|
|
823
823
|
|
|
824
824
|
# An enum labeling different libmognocrypt state machine states
|
|
825
825
|
enum :mongocrypt_ctx_state, [
|
|
826
|
-
:error,
|
|
827
|
-
:need_mongo_collinfo,
|
|
828
|
-
:need_mongo_markings,
|
|
829
|
-
:need_mongo_keys,
|
|
830
|
-
:need_kms,
|
|
831
|
-
:ready,
|
|
832
|
-
:done,
|
|
833
|
-
:need_kms_credentials,
|
|
826
|
+
:error, 0,
|
|
827
|
+
:need_mongo_collinfo, 1,
|
|
828
|
+
:need_mongo_markings, 2,
|
|
829
|
+
:need_mongo_keys, 3,
|
|
830
|
+
:need_kms, 4,
|
|
831
|
+
:ready, 5,
|
|
832
|
+
:done, 6,
|
|
833
|
+
:need_kms_credentials, 7,
|
|
834
|
+
:need_mongo_collinfo_with_db, 8,
|
|
834
835
|
]
|
|
835
836
|
|
|
836
837
|
# @!method self.mongocrypt_ctx_state(ctx)
|
|
@@ -1105,10 +1106,20 @@ module Mongo
|
|
|
1105
1106
|
def self.check_kms_ctx_status(kms_context)
|
|
1106
1107
|
return if yield
|
|
1107
1108
|
|
|
1109
|
+
kms_ctx_status(kms_context).raise_crypt_error(kms: true)
|
|
1110
|
+
end
|
|
1111
|
+
|
|
1112
|
+
# Read the status information for the given KmsContext into a Status
|
|
1113
|
+
# object without raising.
|
|
1114
|
+
#
|
|
1115
|
+
# @param [ Mongo::Crypt::KmsContext ] kms_context
|
|
1116
|
+
#
|
|
1117
|
+
# @return [ Mongo::Crypt::Status ] The KMS context status.
|
|
1118
|
+
def self.kms_ctx_status(kms_context)
|
|
1108
1119
|
status = Status.new
|
|
1109
1120
|
|
|
1110
1121
|
mongocrypt_kms_ctx_status(kms_context.kms_ctx_p, status.ref)
|
|
1111
|
-
status
|
|
1122
|
+
status
|
|
1112
1123
|
end
|
|
1113
1124
|
|
|
1114
1125
|
# @!method self.mongocrypt_kms_ctx_usleep(ctx)
|
|
@@ -1468,6 +1479,31 @@ module Mongo
|
|
|
1468
1479
|
mongocrypt_setopt_bypass_query_analysis(handle.ref)
|
|
1469
1480
|
end
|
|
1470
1481
|
|
|
1482
|
+
# @!method self.mongocrypt_setopt_key_expiration(crypt, cache_expiration_ms)
|
|
1483
|
+
# @api private
|
|
1484
|
+
#
|
|
1485
|
+
# Set the expiration time for the data encryption key cache.
|
|
1486
|
+
#
|
|
1487
|
+
# @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object.
|
|
1488
|
+
# @param [ Integer ] cache_expiration_ms The cache expiration time in
|
|
1489
|
+
# milliseconds. If zero, the cache never expires.
|
|
1490
|
+
# @return [ Boolean ] Returns whether the option was set successfully.
|
|
1491
|
+
attach_function(:mongocrypt_setopt_key_expiration, %i[pointer uint64], :bool)
|
|
1492
|
+
|
|
1493
|
+
# Set the expiration time for the data encryption key cache on the
|
|
1494
|
+
# Mongo::Crypt::Handle object.
|
|
1495
|
+
#
|
|
1496
|
+
# @param [ Mongo::Crypt::Handle ] handle
|
|
1497
|
+
# @param [ Integer ] cache_expiration_ms The cache expiration time in
|
|
1498
|
+
# milliseconds. If zero, the cache never expires.
|
|
1499
|
+
#
|
|
1500
|
+
# @raise [ Mongo::Error::CryptError ] If the option is not set successfully.
|
|
1501
|
+
def self.setopt_key_expiration(handle, cache_expiration_ms)
|
|
1502
|
+
check_status(handle) do
|
|
1503
|
+
mongocrypt_setopt_key_expiration(handle.ref, cache_expiration_ms)
|
|
1504
|
+
end
|
|
1505
|
+
end
|
|
1506
|
+
|
|
1471
1507
|
# @!method self.mongocrypt_setopt_aes_256_ctr(crypt, aes_256_ctr_encrypt, aes_256_ctr_decrypt, ctx)
|
|
1472
1508
|
# @api private
|
|
1473
1509
|
#
|
|
@@ -1646,6 +1682,73 @@ module Mongo
|
|
|
1646
1682
|
mongocrypt_setopt_use_need_kms_credentials_state(handle.ref)
|
|
1647
1683
|
end
|
|
1648
1684
|
|
|
1685
|
+
# @!method self.mongocrypt_setopt_enable_multiple_collinfo(crypt)
|
|
1686
|
+
# @api private
|
|
1687
|
+
#
|
|
1688
|
+
# Enable support for multiple collection schemas. Required to support $lookup.
|
|
1689
|
+
#
|
|
1690
|
+
# @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object.
|
|
1691
|
+
# @pre mongocrypt_init has not been called on crypt.
|
|
1692
|
+
# @return [ Boolean ] Whether the option was set successfully.
|
|
1693
|
+
attach_function(
|
|
1694
|
+
:mongocrypt_setopt_enable_multiple_collinfo,
|
|
1695
|
+
[ :pointer ],
|
|
1696
|
+
:bool
|
|
1697
|
+
)
|
|
1698
|
+
|
|
1699
|
+
# Enable support for multiple collection schemas. Required to support $lookup.
|
|
1700
|
+
#
|
|
1701
|
+
# @param [ Mongo::Crypt::Handle ] handle
|
|
1702
|
+
# @return [ Boolean ] Whether the option was set successfully.
|
|
1703
|
+
def self.setopt_enable_multiple_collinfo(handle)
|
|
1704
|
+
check_status(handle) do
|
|
1705
|
+
mongocrypt_setopt_enable_multiple_collinfo(handle.ref)
|
|
1706
|
+
end
|
|
1707
|
+
end
|
|
1708
|
+
|
|
1709
|
+
# @!method self.mongocrypt_setopt_use_need_mongo_collinfo_with_db_state(crypt)
|
|
1710
|
+
# @api private
|
|
1711
|
+
#
|
|
1712
|
+
# Opt into handling the MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB state.
|
|
1713
|
+
# A context enters this state when processing a bulkWrite command whose
|
|
1714
|
+
# target database differs from the command database ("admin").
|
|
1715
|
+
#
|
|
1716
|
+
# @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object.
|
|
1717
|
+
attach_function(
|
|
1718
|
+
:mongocrypt_setopt_use_need_mongo_collinfo_with_db_state,
|
|
1719
|
+
[ :pointer ],
|
|
1720
|
+
:void
|
|
1721
|
+
)
|
|
1722
|
+
|
|
1723
|
+
# Opt into handling the MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB state.
|
|
1724
|
+
#
|
|
1725
|
+
# @param [ Mongo::Crypt::Handle ] handle
|
|
1726
|
+
def self.setopt_use_need_mongo_collinfo_with_db_state(handle)
|
|
1727
|
+
mongocrypt_setopt_use_need_mongo_collinfo_with_db_state(handle.ref)
|
|
1728
|
+
end
|
|
1729
|
+
|
|
1730
|
+
# @!method self.mongocrypt_ctx_mongo_db(ctx)
|
|
1731
|
+
# @api private
|
|
1732
|
+
#
|
|
1733
|
+
# Get the database name for the current collinfo operation. Used in the
|
|
1734
|
+
# MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB state to determine which
|
|
1735
|
+
# database to run listCollections against.
|
|
1736
|
+
#
|
|
1737
|
+
# @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object.
|
|
1738
|
+
# @return [ String, nil ] The database name, or nil on error.
|
|
1739
|
+
attach_function :mongocrypt_ctx_mongo_db, [ :pointer ], :string
|
|
1740
|
+
|
|
1741
|
+
# Get the database name for the current collinfo operation.
|
|
1742
|
+
#
|
|
1743
|
+
# @param [ Mongo::Crypt::Context ] context
|
|
1744
|
+
# @return [ String ] The database name.
|
|
1745
|
+
def self.ctx_mongo_db(context)
|
|
1746
|
+
db_name = mongocrypt_ctx_mongo_db(context.ctx_p)
|
|
1747
|
+
return db_name if db_name
|
|
1748
|
+
|
|
1749
|
+
check_ctx_status(context)
|
|
1750
|
+
end
|
|
1751
|
+
|
|
1649
1752
|
# @!method self.mongocrypt_ctx_provide_kms_providers(ctx, kms_providers)
|
|
1650
1753
|
# @api private
|
|
1651
1754
|
#
|
|
@@ -1795,6 +1898,46 @@ module Mongo
|
|
|
1795
1898
|
end
|
|
1796
1899
|
end
|
|
1797
1900
|
|
|
1901
|
+
# @!method self.mongocrypt_ctx_setopt_algorithm_text(ctx, opts)
|
|
1902
|
+
# @api private
|
|
1903
|
+
#
|
|
1904
|
+
# Set options for explicit encryption with the "String" algorithm.
|
|
1905
|
+
#
|
|
1906
|
+
# @note The libmongocrypt C function is named `..._algorithm_text` (the
|
|
1907
|
+
# original "text" name), even though the algorithm string passed to
|
|
1908
|
+
# mongocrypt_ctx_setopt_algorithm is "String" (renamed for GA). There is
|
|
1909
|
+
# no `..._algorithm_string` symbol; `..._algorithm_text` is the correct
|
|
1910
|
+
# and only setter for these options.
|
|
1911
|
+
#
|
|
1912
|
+
# @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object.
|
|
1913
|
+
# @param [ FFI::Pointer ] opts A pointer to a string options document.
|
|
1914
|
+
#
|
|
1915
|
+
# @return [ Boolean ] Whether setting this option succeeded.
|
|
1916
|
+
attach_function(
|
|
1917
|
+
:mongocrypt_ctx_setopt_algorithm_text,
|
|
1918
|
+
%i[
|
|
1919
|
+
pointer
|
|
1920
|
+
pointer
|
|
1921
|
+
],
|
|
1922
|
+
:bool
|
|
1923
|
+
)
|
|
1924
|
+
|
|
1925
|
+
# Set options for explicit encryption with the "String" algorithm.
|
|
1926
|
+
#
|
|
1927
|
+
# @param [ Mongo::Crypt::Context ] context
|
|
1928
|
+
# @param [ Hash ] opts options
|
|
1929
|
+
#
|
|
1930
|
+
# @raise [ Mongo::Error::CryptError ] If the operation failed
|
|
1931
|
+
def self.ctx_setopt_algorithm_text(context, opts)
|
|
1932
|
+
validate_document(opts)
|
|
1933
|
+
data = opts.to_bson.to_s
|
|
1934
|
+
Binary.wrap_string(data) do |data_p|
|
|
1935
|
+
check_ctx_status(context) do
|
|
1936
|
+
mongocrypt_ctx_setopt_algorithm_text(context.ctx_p, data_p)
|
|
1937
|
+
end
|
|
1938
|
+
end
|
|
1939
|
+
end
|
|
1940
|
+
|
|
1798
1941
|
# Raise a Mongo::Error::CryptError based on the status of the underlying
|
|
1799
1942
|
# mongocrypt_t object.
|
|
1800
1943
|
#
|
data/lib/mongo/crypt/context.rb
CHANGED
|
@@ -76,7 +76,7 @@ module Mongo
|
|
|
76
76
|
# This method is not currently unit tested. It is integration tested
|
|
77
77
|
# in spec/integration/explicit_encryption_spec.rb
|
|
78
78
|
def run_state_machine(timeout_holder)
|
|
79
|
-
|
|
79
|
+
loop do
|
|
80
80
|
timeout_ms = timeout_holder.remaining_timeout_ms!
|
|
81
81
|
case state
|
|
82
82
|
when :error
|
|
@@ -90,6 +90,8 @@ module Mongo
|
|
|
90
90
|
provide_keys(timeout_ms)
|
|
91
91
|
when :need_mongo_collinfo
|
|
92
92
|
provide_collection_info(timeout_ms)
|
|
93
|
+
when :need_mongo_collinfo_with_db
|
|
94
|
+
provide_collection_info_with_db(timeout_ms)
|
|
93
95
|
when :need_mongo_markings
|
|
94
96
|
provide_markings(timeout_ms)
|
|
95
97
|
when :need_kms
|
|
@@ -122,17 +124,26 @@ module Mongo
|
|
|
122
124
|
def provide_markings(timeout_ms)
|
|
123
125
|
cmd = Binding.ctx_mongo_op(self)
|
|
124
126
|
|
|
125
|
-
result = @encryption_io.mark_command(cmd, timeout_ms: timeout_ms)
|
|
127
|
+
result = @encryption_io.mark_command(cmd, db_name: @db_name, timeout_ms: timeout_ms)
|
|
126
128
|
mongocrypt_feed(result)
|
|
127
129
|
|
|
128
130
|
mongocrypt_done
|
|
129
131
|
end
|
|
130
132
|
|
|
131
133
|
def provide_collection_info(timeout_ms)
|
|
134
|
+
feed_collection_info(@db_name, timeout_ms)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def provide_collection_info_with_db(timeout_ms)
|
|
138
|
+
feed_collection_info(Binding.ctx_mongo_db(self), timeout_ms)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def feed_collection_info(db_name, timeout_ms)
|
|
132
142
|
filter = Binding.ctx_mongo_op(self)
|
|
133
143
|
|
|
134
|
-
|
|
135
|
-
|
|
144
|
+
@encryption_io.collection_info(db_name, filter, timeout_ms: timeout_ms).each do |result|
|
|
145
|
+
mongocrypt_feed(result)
|
|
146
|
+
end
|
|
136
147
|
|
|
137
148
|
mongocrypt_done
|
|
138
149
|
end
|
|
@@ -159,12 +170,29 @@ module Mongo
|
|
|
159
170
|
raise unless e.network_error?
|
|
160
171
|
next if Binding.kms_ctx_fail(kms_context)
|
|
161
172
|
|
|
162
|
-
|
|
173
|
+
raise_kms_retry_error(kms_context, e)
|
|
163
174
|
end
|
|
164
175
|
end
|
|
165
176
|
Binding.ctx_kms_done(self)
|
|
166
177
|
end
|
|
167
178
|
|
|
179
|
+
# Raise a KmsError that wraps the KMS status message (which describes the
|
|
180
|
+
# retry exhaustion) with the error from the last attempt.
|
|
181
|
+
#
|
|
182
|
+
# @param [ Mongo::Crypt::KmsContext ] kms_context
|
|
183
|
+
# @param [ Mongo::Error::KmsError ] last_error The error from the last
|
|
184
|
+
# KMS request attempt.
|
|
185
|
+
#
|
|
186
|
+
# @raise [ Mongo::Error::KmsError ]
|
|
187
|
+
def raise_kms_retry_error(kms_context, last_error)
|
|
188
|
+
status = Binding.kms_ctx_status(kms_context)
|
|
189
|
+
raise Error::KmsError.new(
|
|
190
|
+
"#{status.message}, last attempt failed with: #{last_error.message}",
|
|
191
|
+
code: status.code,
|
|
192
|
+
network_error: true
|
|
193
|
+
)
|
|
194
|
+
end
|
|
195
|
+
|
|
168
196
|
# Indicate that state machine is done feeding I/O responses back to libmongocrypt
|
|
169
197
|
def mongocrypt_done
|
|
170
198
|
Binding.mongocrypt_ctx_mongo_done(ctx_p)
|
|
@@ -188,21 +216,24 @@ module Mongo
|
|
|
188
216
|
# KMS providers.
|
|
189
217
|
def retrieve_kms_credentials(timeout_holder)
|
|
190
218
|
providers = {}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
219
|
+
kms_providers.credentials_map.each do |identifier, creds|
|
|
220
|
+
next unless creds.empty?
|
|
221
|
+
|
|
222
|
+
case KMS.provider_base_type(identifier)
|
|
223
|
+
when 'aws'
|
|
224
|
+
begin
|
|
225
|
+
aws_credentials = Mongo::Auth::Aws::CredentialsRetriever.new.credentials(timeout_holder)
|
|
226
|
+
rescue Auth::Aws::CredentialsNotFound
|
|
227
|
+
raise Error::CryptError.new(
|
|
228
|
+
'Could not locate AWS credentials (checked environment variables, ECS and EC2 metadata)'
|
|
229
|
+
)
|
|
230
|
+
end
|
|
231
|
+
providers[identifier] = aws_credentials.to_h
|
|
232
|
+
when 'gcp'
|
|
233
|
+
providers[identifier] = { access_token: gcp_access_token(timeout_holder) }
|
|
234
|
+
when 'azure'
|
|
235
|
+
providers[identifier] = { access_token: azure_access_token(timeout_holder) }
|
|
198
236
|
end
|
|
199
|
-
providers[:aws] = aws_credentials.to_h
|
|
200
|
-
end
|
|
201
|
-
if kms_providers.gcp && kms_providers.gcp.empty?
|
|
202
|
-
providers[:gcp] = { access_token: gcp_access_token(timeout_holder) }
|
|
203
|
-
end
|
|
204
|
-
if kms_providers.azure && kms_providers.azure.empty?
|
|
205
|
-
providers[:azure] = { access_token: azure_access_token(timeout_holder) }
|
|
206
237
|
end
|
|
207
238
|
KMS::Credentials.new(providers)
|
|
208
239
|
end
|
|
@@ -97,7 +97,7 @@ module Mongo
|
|
|
97
97
|
# Must be a non-negative integer. An explicit value of 0 means infinite.
|
|
98
98
|
# The default value is unset which means the feature is not enabled.
|
|
99
99
|
#
|
|
100
|
-
# @return [
|
|
100
|
+
# @return [ Array<BSON::Document> ] The collection information documents
|
|
101
101
|
def collection_info(db_name, filter, timeout_ms: nil)
|
|
102
102
|
unless @metadata_client
|
|
103
103
|
raise ArgumentError,
|
|
@@ -108,18 +108,22 @@ module Mongo
|
|
|
108
108
|
.use(db_name)
|
|
109
109
|
.database
|
|
110
110
|
.list_collections(filter: filter, deserialize_as_bson: true, timeout_ms: timeout_ms)
|
|
111
|
-
.
|
|
111
|
+
.to_a
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
# Send the command to mongocryptd to be marked with intent-to-encrypt markings
|
|
115
115
|
#
|
|
116
116
|
# @param [ Hash ] cmd
|
|
117
|
+
# @param [ String | nil ] :db_name The database against which the command
|
|
118
|
+
# is being run. When provided, the command is sent to mongocryptd using
|
|
119
|
+
# this database so that the namespace in the command matches the namespace
|
|
120
|
+
# in encryptionInformation.
|
|
117
121
|
# @param [ Integer ] :timeout_ms The operation timeout in milliseconds.
|
|
118
122
|
# Must be a non-negative integer. An explicit value of 0 means infinite.
|
|
119
123
|
# The default value is unset which means the feature is not enabled.
|
|
120
124
|
#
|
|
121
125
|
# @return [ Hash ] The marked command
|
|
122
|
-
def mark_command(cmd, timeout_ms: nil)
|
|
126
|
+
def mark_command(cmd, db_name: nil, timeout_ms: nil)
|
|
123
127
|
unless @mongocryptd_client
|
|
124
128
|
raise ArgumentError,
|
|
125
129
|
'mark_command requires mongocryptd_client to have been passed to the constructor, but it was not'
|
|
@@ -132,13 +136,15 @@ module Mongo
|
|
|
132
136
|
timeout_ms: timeout_ms
|
|
133
137
|
}
|
|
134
138
|
|
|
139
|
+
mongocryptd = db_name ? @mongocryptd_client.use(db_name) : @mongocryptd_client
|
|
140
|
+
|
|
135
141
|
begin
|
|
136
|
-
response =
|
|
142
|
+
response = mongocryptd.database.command(cmd, options)
|
|
137
143
|
rescue Error::NoServerAvailable => e
|
|
138
144
|
raise e if @options[:mongocryptd_bypass_spawn]
|
|
139
145
|
|
|
140
146
|
spawn_mongocryptd
|
|
141
|
-
response =
|
|
147
|
+
response = mongocryptd.database.command(cmd, options)
|
|
142
148
|
end
|
|
143
149
|
|
|
144
150
|
response.first
|
|
@@ -37,12 +37,19 @@ module Mongo
|
|
|
37
37
|
# to TLS connection options of Mongo::Client.
|
|
38
38
|
# @param [ Integer | nil ] timeout_ms Timeout for every operation executed
|
|
39
39
|
# on this object.
|
|
40
|
-
|
|
40
|
+
# @param [ Integer | nil ] key_expiration_ms The lifetime of the data
|
|
41
|
+
# encryption key cache, in milliseconds. A value of 0 means the cache
|
|
42
|
+
# never expires. When nil, libmongocrypt's default of 60000 is used.
|
|
43
|
+
def initialize(
|
|
44
|
+
key_vault_client, key_vault_namespace, kms_providers, kms_tls_options,
|
|
45
|
+
timeout_ms = nil, key_expiration_ms = nil
|
|
46
|
+
)
|
|
41
47
|
Crypt.validate_ffi!
|
|
42
48
|
@crypt_handle = Handle.new(
|
|
43
49
|
kms_providers,
|
|
44
50
|
kms_tls_options,
|
|
45
|
-
explicit_encryption_only: true
|
|
51
|
+
explicit_encryption_only: true,
|
|
52
|
+
key_expiration_ms: key_expiration_ms
|
|
46
53
|
)
|
|
47
54
|
@encryption_io = EncryptionIO.new(
|
|
48
55
|
key_vault_client: key_vault_client,
|
|
@@ -93,23 +100,31 @@ module Mongo
|
|
|
93
100
|
# encryption key.
|
|
94
101
|
# @option options [ String ] :algorithm The algorithm used to encrypt the value.
|
|
95
102
|
# Valid algorithms are "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic",
|
|
96
|
-
# "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed"
|
|
103
|
+
# "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed",
|
|
104
|
+
# "Range", "String".
|
|
97
105
|
# @option options [ Integer | nil ] :contention_factor Contention factor
|
|
98
|
-
# to be applied if encryption algorithm is set to "Indexed"
|
|
99
|
-
# provided, it defaults to a value of 0. Contention
|
|
100
|
-
# only if encryption algorithm is set to "Indexed"
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
106
|
+
# to be applied if encryption algorithm is set to "Indexed", "Range", or
|
|
107
|
+
# "String". If not provided, it defaults to a value of 0. Contention
|
|
108
|
+
# factor should be set only if encryption algorithm is set to "Indexed",
|
|
109
|
+
# "Range", or "String".
|
|
110
|
+
# @option options [ String | nil ] :query_type Query type to be applied
|
|
111
|
+
# if encryption algorithm is set to "Indexed", "Range", or "String".
|
|
112
|
+
# Allowed values are "equality" (for "Indexed"), "range" (for "Range"),
|
|
113
|
+
# and "prefix", "suffix", "substring" (for "String").
|
|
114
|
+
# @option options [ Hash | nil ] :range_opts Specifies index options for a
|
|
115
|
+
# Queryable Encryption field supporting "range" queries. Required when
|
|
116
|
+
# algorithm is "Range".
|
|
117
|
+
# @option options [ Hash | nil ] :string_opts Specifies index options for a
|
|
118
|
+
# Queryable Encryption field supporting "prefix", "suffix", or
|
|
119
|
+
# "substring" queries. Required when algorithm is "String".
|
|
105
120
|
#
|
|
106
121
|
# @note The :key_id and :key_alt_name options are mutually exclusive. Only
|
|
107
122
|
# one is required to perform explicit encryption.
|
|
108
123
|
#
|
|
109
124
|
# @return [ BSON::Binary ] A BSON Binary object of subtype 6 (ciphertext)
|
|
110
125
|
# representing the encrypted value
|
|
111
|
-
# @raise [ ArgumentError ] if either contention_factor or query_type
|
|
112
|
-
#
|
|
126
|
+
# @raise [ ArgumentError ] if either contention_factor or query_type is
|
|
127
|
+
# set, and algorithm is not "Indexed", "Range", or "String".
|
|
113
128
|
def encrypt(value, options)
|
|
114
129
|
Crypt::ExplicitEncryptionContext.new(
|
|
115
130
|
@crypt_handle,
|
|
@@ -36,14 +36,16 @@ module Mongo
|
|
|
36
36
|
# that will be used to encrypt the value.
|
|
37
37
|
# @option options [ String ] :algorithm The algorithm used to encrypt the
|
|
38
38
|
# value. Valid algorithms are "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic",
|
|
39
|
-
# "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed", "Range"
|
|
39
|
+
# "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed", "Range",
|
|
40
|
+
# "String".
|
|
40
41
|
# @option options [ Integer | nil ] :contention_factor Contention factor
|
|
41
|
-
# to be applied if encryption algorithm is set to "Indexed"
|
|
42
|
-
# provided, it defaults to a value of 0. Contention factor should be set
|
|
43
|
-
# only if encryption algorithm is set to "Indexed".
|
|
42
|
+
# to be applied if encryption algorithm is set to "Indexed", "Range", or "String".
|
|
43
|
+
# If not provided, it defaults to a value of 0. Contention factor should be set
|
|
44
|
+
# only if encryption algorithm is set to "Indexed", "Range", or "String".
|
|
44
45
|
# @option options [ String | nil ] query_type Query type to be applied
|
|
45
|
-
# if encryption algorithm is set to "Indexed" or "
|
|
46
|
-
# Allowed values are "equality"
|
|
46
|
+
# if encryption algorithm is set to "Indexed", "Range", or "String".
|
|
47
|
+
# Allowed values are "equality", "range", "prefix", "suffix", and
|
|
48
|
+
# "substring".
|
|
47
49
|
# @option options [ Hash | nil ] :range_opts Specifies index options for
|
|
48
50
|
# a Queryable Encryption field supporting "range" queries.
|
|
49
51
|
# Allowed options are:
|
|
@@ -56,9 +58,22 @@ module Mongo
|
|
|
56
58
|
# the encryptedFields of the destination collection.
|
|
57
59
|
# For double and decimal128, min/max/precision must all be set,
|
|
58
60
|
# or all be unset.
|
|
61
|
+
# @option options [ Hash | nil ] :string_opts Specifies index options for
|
|
62
|
+
# a Queryable Encryption field supporting "prefix", "suffix", or
|
|
63
|
+
# "substring" queries (algorithm "String"). Allowed options are:
|
|
64
|
+
# - :case_sensitive
|
|
65
|
+
# - :diacritic_sensitive
|
|
66
|
+
# - :prefix (Hash with :str_min_query_length, :str_max_query_length)
|
|
67
|
+
# - :suffix (Hash with :str_min_query_length, :str_max_query_length)
|
|
68
|
+
# - :substring (Hash with :str_max_length, :str_min_query_length,
|
|
69
|
+
# :str_max_query_length)
|
|
70
|
+
# The options must match the values set in the encryptedFields of the
|
|
71
|
+
# destination collection.
|
|
59
72
|
#
|
|
60
73
|
# @note The Range algorithm is experimental only. It is not intended for
|
|
61
74
|
# public use.
|
|
75
|
+
# @note The "substring" query type is unstable and subject to backwards
|
|
76
|
+
# breaking changes.
|
|
62
77
|
#
|
|
63
78
|
# @raise [ ArgumentError|Mongo::Error::CryptError ] If invalid options are provided
|
|
64
79
|
def initialize(mongocrypt, io, doc, options = {})
|
|
@@ -114,28 +129,52 @@ module Mongo
|
|
|
114
129
|
|
|
115
130
|
def set_algorithm_opts(options)
|
|
116
131
|
Binding.ctx_setopt_algorithm(self, options[:algorithm])
|
|
117
|
-
if %w[Indexed Range].include?(options[:algorithm])
|
|
132
|
+
if %w[Indexed Range String].include?(options[:algorithm])
|
|
118
133
|
Binding.ctx_setopt_contention_factor(self, options[:contention_factor]) if options[:contention_factor]
|
|
119
134
|
Binding.ctx_setopt_query_type(self, options[:query_type]) if options[:query_type]
|
|
120
135
|
else
|
|
121
136
|
if options[:contention_factor]
|
|
122
|
-
raise ArgumentError.new(':contention_factor is allowed only for "Indexed" or "
|
|
137
|
+
raise ArgumentError.new(':contention_factor is allowed only for "Indexed", "Range", or "String" algorithms')
|
|
123
138
|
end
|
|
124
139
|
if options[:query_type]
|
|
125
|
-
raise ArgumentError.new(':query_type is allowed only for "Indexed" or "
|
|
140
|
+
raise ArgumentError.new(':query_type is allowed only for "Indexed", "Range", or "String" algorithms')
|
|
126
141
|
end
|
|
127
142
|
end
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
143
|
+
if options[:algorithm] == 'Range'
|
|
144
|
+
Binding.ctx_setopt_algorithm_range(self, convert_range_opts(options[:range_opts]))
|
|
145
|
+
elsif options[:algorithm] == 'String'
|
|
146
|
+
Binding.ctx_setopt_algorithm_text(self, convert_string_opts(options[:string_opts]))
|
|
147
|
+
end
|
|
131
148
|
end
|
|
132
149
|
|
|
133
150
|
def convert_range_opts(range_opts)
|
|
151
|
+
raise ArgumentError.new(':range_opts is required for the "Range" algorithm') if range_opts.nil?
|
|
152
|
+
|
|
134
153
|
range_opts.dup.tap do |opts|
|
|
135
154
|
opts[:sparsity] = BSON::Int64.new(opts[:sparsity]) if opts[:sparsity] && !opts[:sparsity].is_a?(BSON::Int64)
|
|
136
155
|
opts[:trimFactor] = opts.delete(:trim_factor) if opts[:trim_factor]
|
|
137
156
|
end
|
|
138
157
|
end
|
|
158
|
+
|
|
159
|
+
def convert_string_opts(string_opts)
|
|
160
|
+
raise ArgumentError.new(':string_opts is required for the "String" algorithm') if string_opts.nil?
|
|
161
|
+
|
|
162
|
+
string_opts.dup.tap do |opts|
|
|
163
|
+
opts[:caseSensitive] = opts.delete(:case_sensitive) if opts.key?(:case_sensitive)
|
|
164
|
+
opts[:diacriticSensitive] = opts.delete(:diacritic_sensitive) if opts.key?(:diacritic_sensitive)
|
|
165
|
+
%i[substring prefix suffix].each do |query_type|
|
|
166
|
+
opts[query_type] = convert_string_query_opts(opts[query_type]) if opts[query_type]
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def convert_string_query_opts(query_opts)
|
|
172
|
+
query_opts.dup.tap do |opts|
|
|
173
|
+
opts[:strMaxLength] = opts.delete(:str_max_length) if opts.key?(:str_max_length)
|
|
174
|
+
opts[:strMinQueryLength] = opts.delete(:str_min_query_length) if opts.key?(:str_min_query_length)
|
|
175
|
+
opts[:strMaxQueryLength] = opts.delete(:str_max_query_length) if opts.key?(:str_max_query_length)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
139
178
|
end
|
|
140
179
|
end
|
|
141
180
|
end
|
data/lib/mongo/crypt/handle.rb
CHANGED
|
@@ -66,6 +66,10 @@ module Mongo
|
|
|
66
66
|
# error that libmongocrypt raises on a subsequent "$SYSTEM" search.
|
|
67
67
|
# @option options [ Logger ] :logger A Logger object to which libmongocrypt logs
|
|
68
68
|
# will be sent
|
|
69
|
+
# @option options [ Integer | nil ] :key_expiration_ms The lifetime of the
|
|
70
|
+
# data encryption key cache, in milliseconds. A value of 0 means the
|
|
71
|
+
# cache never expires. When nil, libmongocrypt's default of 60000 is
|
|
72
|
+
# used.
|
|
69
73
|
def initialize(kms_providers, kms_tls_options, options = {})
|
|
70
74
|
# FFI::AutoPointer uses a custom release strategy to automatically free
|
|
71
75
|
# the pointer once this object goes out of scope
|
|
@@ -74,6 +78,8 @@ module Mongo
|
|
|
74
78
|
Binding.method(:mongocrypt_destroy)
|
|
75
79
|
)
|
|
76
80
|
Binding.kms_ctx_setopt_retry_kms(self, true)
|
|
81
|
+
Binding.setopt_enable_multiple_collinfo(self)
|
|
82
|
+
Binding.setopt_use_need_mongo_collinfo_with_db_state(self)
|
|
77
83
|
@kms_providers = kms_providers
|
|
78
84
|
@kms_tls_options = kms_tls_options
|
|
79
85
|
|
|
@@ -85,6 +91,9 @@ module Mongo
|
|
|
85
91
|
@bypass_query_analysis = options[:bypass_query_analysis]
|
|
86
92
|
set_bypass_query_analysis if @bypass_query_analysis
|
|
87
93
|
|
|
94
|
+
@key_expiration_ms = options[:key_expiration_ms]
|
|
95
|
+
set_key_expiration unless @key_expiration_ms.nil?
|
|
96
|
+
|
|
88
97
|
@crypt_shared_lib_path = options[:crypt_shared_lib_path]
|
|
89
98
|
@explicit_encryption_only = options[:explicit_encryption_only]
|
|
90
99
|
@disable_crypt_shared_lib_search = options[:disable_crypt_shared_lib_search]
|
|
@@ -101,7 +110,7 @@ module Mongo
|
|
|
101
110
|
|
|
102
111
|
Binding.setopt_kms_providers(self, @kms_providers.to_document)
|
|
103
112
|
|
|
104
|
-
if @kms_providers.
|
|
113
|
+
if @kms_providers.any_on_demand?
|
|
105
114
|
Binding.setopt_use_need_kms_credentials_state(self)
|
|
106
115
|
end
|
|
107
116
|
|
|
@@ -123,13 +132,23 @@ module Mongo
|
|
|
123
132
|
end
|
|
124
133
|
|
|
125
134
|
# Return TLS options for KMS provider. If there are no TLS options set,
|
|
126
|
-
# empty hash is returned.
|
|
135
|
+
# empty hash is returned. Named providers (e.g. "kmip:name1") fall back
|
|
136
|
+
# to the base-type options (e.g. :kmip) when no exact match is found.
|
|
127
137
|
#
|
|
128
|
-
# @param [ String ] provider
|
|
138
|
+
# @param [ String ] provider KMS provider name or named identifier.
|
|
129
139
|
#
|
|
130
140
|
# @return [ Hash ] TLS options to connect to KMS provider.
|
|
131
141
|
def kms_tls_options(provider)
|
|
132
|
-
|
|
142
|
+
provider_str = provider.to_s
|
|
143
|
+
base_type = KMS.provider_base_type(provider_str)
|
|
144
|
+
|
|
145
|
+
@kms_tls_options.fetch(provider_str) do
|
|
146
|
+
@kms_tls_options.fetch(provider_str.to_sym) do
|
|
147
|
+
@kms_tls_options.fetch(base_type) do
|
|
148
|
+
@kms_tls_options.fetch(base_type.to_sym, {})
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
133
152
|
end
|
|
134
153
|
|
|
135
154
|
def crypt_shared_lib_version
|
|
@@ -188,10 +207,21 @@ module Mongo
|
|
|
188
207
|
Binding.setopt_bypass_query_analysis(self) if @bypass_query_analysis
|
|
189
208
|
end
|
|
190
209
|
|
|
210
|
+
def set_key_expiration
|
|
211
|
+
unless @key_expiration_ms.is_a?(Integer) && !@key_expiration_ms.negative?
|
|
212
|
+
raise ArgumentError.new(
|
|
213
|
+
"#{@key_expiration_ms} is an invalid key_expiration_ms value; " \
|
|
214
|
+
'must be a non-negative Integer or nil'
|
|
215
|
+
)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
Binding.setopt_key_expiration(self, @key_expiration_ms)
|
|
219
|
+
end
|
|
220
|
+
|
|
191
221
|
# Send the logs from libmongocrypt to the Mongo::Logger
|
|
192
222
|
def set_logger_callback
|
|
193
223
|
@log_callback = proc do |level, msg|
|
|
194
|
-
@logger.
|
|
224
|
+
@logger.public_send(level, msg)
|
|
195
225
|
end
|
|
196
226
|
|
|
197
227
|
Binding.setopt_log_handler(@mongocrypt, @log_callback)
|