couchbase 3.8.1 → 3.9.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.
Files changed (475) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/ext/CMakeLists.txt +16 -1
  4. data/ext/cache/cpm/{CPM_0.42.0.cmake → CPM_0.43.1.cmake} +18 -2
  5. data/ext/cache/extconf_include.rb +6 -6
  6. data/ext/cache/mozilla-ca-bundle.crt +214 -155
  7. data/ext/cache/mozilla-ca-bundle.sha256 +1 -1
  8. data/ext/couchbase/CMakeLists.txt +130 -21
  9. data/ext/couchbase/cmake/APKBUILD.in +25 -4
  10. data/ext/couchbase/cmake/Bundler.cmake +143 -34
  11. data/ext/couchbase/cmake/CPM.cmake +126 -12
  12. data/ext/couchbase/cmake/Cache.cmake +7 -1
  13. data/ext/couchbase/cmake/CompilerOptions.cmake +1 -1
  14. data/ext/couchbase/cmake/CompilerWarnings.cmake +17 -6
  15. data/ext/couchbase/cmake/GrpcProtobuf.cmake +481 -0
  16. data/ext/couchbase/cmake/OpenSSL.cmake +10 -3
  17. data/ext/couchbase/cmake/Packaging.cmake +519 -65
  18. data/ext/couchbase/cmake/Protostellar.cmake +250 -0
  19. data/ext/couchbase/cmake/Sanitizers.cmake +61 -50
  20. data/ext/couchbase/cmake/StaticAnalyzers.cmake +16 -5
  21. data/ext/couchbase/cmake/TestFramework.cmake +283 -0
  22. data/ext/couchbase/cmake/TestFrameworkAddTests.cmake +51 -0
  23. data/ext/couchbase/cmake/Testing.cmake +27 -9
  24. data/ext/couchbase/cmake/ThirdPartyDependencies.cmake +141 -8
  25. data/ext/couchbase/cmake/VersionInfo.cmake +50 -0
  26. data/ext/couchbase/cmake/build_config.hxx.in +2 -1
  27. data/ext/couchbase/cmake/couchbase-cxx-client.spec.in +101 -23
  28. data/ext/couchbase/cmake/couchbase_cxx_client_static-config.cmake.in +7 -0
  29. data/ext/couchbase/cmake/couchbase_cxx_client_static.pc.in +2 -0
  30. data/ext/couchbase/cmake/gRPC_Generate.cmake +58 -0
  31. data/ext/couchbase/cmake/protobuf_Generate.cmake +69 -0
  32. data/ext/couchbase/cmake/require-git-checkout.cmake +25 -0
  33. data/ext/couchbase/cmake/tarball_glob.txt +49 -0
  34. data/ext/couchbase/core/agent_group.cxx +0 -8
  35. data/ext/couchbase/core/agent_group.hxx +0 -4
  36. data/ext/couchbase/core/analytics_stream.cxx +314 -0
  37. data/ext/couchbase/core/analytics_stream.hxx +129 -0
  38. data/ext/couchbase/core/analytics_stream_component.cxx +245 -0
  39. data/ext/couchbase/core/analytics_stream_component.hxx +67 -0
  40. data/ext/couchbase/core/app_telemetry_meter.cxx +25 -3
  41. data/ext/couchbase/core/app_telemetry_meter.hxx +77 -0
  42. data/ext/couchbase/core/app_telemetry_reporter.cxx +7 -6
  43. data/ext/couchbase/core/bucket.cxx +381 -87
  44. data/ext/couchbase/core/bucket.hxx +105 -25
  45. data/ext/couchbase/core/bucket_unit_test_api.hxx +57 -0
  46. data/ext/couchbase/core/capella.hxx +91 -0
  47. data/ext/couchbase/core/cluster.cxx +1554 -398
  48. data/ext/couchbase/core/cluster.hxx +39 -8
  49. data/ext/couchbase/core/cluster_agent.cxx +0 -8
  50. data/ext/couchbase/core/cluster_agent.hxx +0 -4
  51. data/ext/couchbase/core/cluster_options.hxx +26 -3
  52. data/ext/couchbase/core/collections_component.cxx +39 -15
  53. data/ext/couchbase/core/config_profiles.cxx +1 -1
  54. data/ext/couchbase/core/crud_component.cxx +2 -2
  55. data/ext/couchbase/core/crud_options.hxx +34 -0
  56. data/ext/couchbase/core/crypto/cbcrypto.cc +18 -13
  57. data/ext/couchbase/core/document_id_redaction.hxx +81 -0
  58. data/ext/couchbase/core/error_context/base_error_context.hxx +7 -6
  59. data/ext/couchbase/core/error_context/key_value.cxx +25 -2
  60. data/ext/couchbase/core/error_context/key_value.hxx +60 -33
  61. data/ext/couchbase/core/error_context/key_value_error_context.hxx +16 -15
  62. data/ext/couchbase/core/error_context/search.hxx +5 -0
  63. data/ext/couchbase/core/error_context/search_json.hxx +3 -0
  64. data/ext/couchbase/core/error_context/subdocument_error_context.hxx +19 -18
  65. data/ext/couchbase/core/error_context/view.hxx +5 -0
  66. data/ext/couchbase/core/free_form_http_request.cxx +237 -4
  67. data/ext/couchbase/core/free_form_http_request.hxx +41 -8
  68. data/ext/couchbase/core/http_component.cxx +300 -284
  69. data/ext/couchbase/core/http_component.hxx +0 -4
  70. data/ext/couchbase/core/impl/analytics.cxx +38 -5
  71. data/ext/couchbase/core/impl/analytics.hxx +34 -0
  72. data/ext/couchbase/core/impl/bootstrap_error.hxx +0 -3
  73. data/ext/couchbase/core/impl/collection.cxx +215 -202
  74. data/ext/couchbase/core/impl/collection_manager.cxx +1 -1
  75. data/ext/couchbase/core/impl/dns_srv_tracker.cxx +45 -18
  76. data/ext/couchbase/core/impl/error.cxx +22 -2
  77. data/ext/couchbase/core/impl/internal_analytics_stream_result.hxx +111 -0
  78. data/ext/couchbase/core/impl/internal_query_stream_result.hxx +110 -0
  79. data/ext/couchbase/core/impl/invoke_with_node_id.hxx +15 -7
  80. data/ext/couchbase/core/impl/key_value_error_category.cxx +6 -0
  81. data/ext/couchbase/core/impl/lookup_in_replica.cxx +10 -10
  82. data/ext/couchbase/core/impl/lookup_in_replica.hxx +4 -4
  83. data/ext/couchbase/core/impl/node_id.cxx +49 -13
  84. data/ext/couchbase/core/impl/observability_recorder.cxx +12 -2
  85. data/ext/couchbase/core/impl/observe_poll.cxx +63 -23
  86. data/ext/couchbase/core/impl/public_analytics_stream_result.cxx +372 -0
  87. data/ext/couchbase/core/impl/public_bucket.cxx +48 -2
  88. data/ext/couchbase/core/impl/public_cluster.cxx +325 -25
  89. data/ext/couchbase/core/impl/public_query_stream_result.cxx +380 -0
  90. data/ext/couchbase/core/impl/public_transaction_get_multi_replicas_from_preferred_server_group_result.cxx +42 -0
  91. data/ext/couchbase/core/impl/public_transaction_get_multi_result.cxx +42 -0
  92. data/ext/couchbase/core/impl/query.cxx +76 -7
  93. data/ext/couchbase/core/impl/query.hxx +36 -0
  94. data/ext/couchbase/core/impl/query_index_manager.cxx +54 -1
  95. data/ext/couchbase/core/impl/replica_utils.cxx +101 -0
  96. data/ext/couchbase/core/impl/replica_utils.hxx +64 -0
  97. data/ext/couchbase/core/impl/resolve_node_id.hxx +125 -0
  98. data/ext/couchbase/core/impl/scope.cxx +70 -0
  99. data/ext/couchbase/core/impl/search.cxx +31 -1
  100. data/ext/couchbase/core/impl/transactions.cxx +130 -0
  101. data/ext/couchbase/core/impl/wait_until_ready.cxx +439 -0
  102. data/ext/couchbase/core/impl/wait_until_ready.hxx +110 -0
  103. data/ext/couchbase/core/impl/with_bucket_config_or_timeout.hxx +180 -0
  104. data/ext/couchbase/core/impl/with_cancellation.hxx +11 -3
  105. data/ext/couchbase/core/io/configuration_belongs_to_session.hxx +66 -0
  106. data/ext/couchbase/core/io/dns_client.cxx +122 -45
  107. data/ext/couchbase/core/io/dns_codec.hxx +21 -21
  108. data/ext/couchbase/core/io/dns_config.cxx +21 -6
  109. data/ext/couchbase/core/io/http_command.hxx +149 -121
  110. data/ext/couchbase/core/io/http_error.hxx +107 -0
  111. data/ext/couchbase/core/io/http_parser.cxx +4 -0
  112. data/ext/couchbase/core/io/http_session.cxx +606 -154
  113. data/ext/couchbase/core/io/http_session.hxx +147 -30
  114. data/ext/couchbase/core/io/http_session_manager.hxx +329 -382
  115. data/ext/couchbase/core/io/http_streaming_parser.cxx +4 -0
  116. data/ext/couchbase/core/io/http_streaming_response.cxx +328 -43
  117. data/ext/couchbase/core/io/http_streaming_response.hxx +37 -3
  118. data/ext/couchbase/core/io/mcbp_buffer_pool.hxx +122 -0
  119. data/ext/couchbase/core/io/mcbp_command.hxx +150 -51
  120. data/ext/couchbase/core/io/mcbp_command_id.hxx +45 -0
  121. data/ext/couchbase/core/io/mcbp_message.cxx +9 -1
  122. data/ext/couchbase/core/io/mcbp_message.hxx +19 -0
  123. data/ext/couchbase/core/io/mcbp_output_queue.hxx +150 -0
  124. data/ext/couchbase/core/io/mcbp_parser.cxx +24 -7
  125. data/ext/couchbase/core/io/mcbp_session.cxx +316 -317
  126. data/ext/couchbase/core/io/mcbp_session.hxx +27 -18
  127. data/ext/couchbase/core/io/opaque_ring_table.hxx +145 -0
  128. data/ext/couchbase/core/io/retry_orchestrator.hxx +6 -5
  129. data/ext/couchbase/core/io/stream_deadline.hxx +50 -0
  130. data/ext/couchbase/core/io/streams.cxx +275 -14
  131. data/ext/couchbase/core/io/streams.hxx +53 -0
  132. data/ext/couchbase/core/logger/logger.cxx +36 -0
  133. data/ext/couchbase/core/logger/logger.hxx +46 -6
  134. data/ext/couchbase/core/logger/redaction.hxx +503 -0
  135. data/ext/couchbase/core/management/eventing_status.hxx +10 -0
  136. data/ext/couchbase/core/management/eventing_status_json.hxx +4 -0
  137. data/ext/couchbase/core/mcbp/big_endian.cxx +1 -1
  138. data/ext/couchbase/core/mcbp/codec.cxx +27 -18
  139. data/ext/couchbase/core/mcbp/operation_queue.cxx +2 -2
  140. data/ext/couchbase/core/mcbp/packet.hxx +2 -2
  141. data/ext/couchbase/core/mcbp/queue_request.cxx +23 -6
  142. data/ext/couchbase/core/mcbp/queue_request.hxx +21 -0
  143. data/ext/couchbase/core/meta/features.hxx +13 -0
  144. data/ext/couchbase/core/meta/version.cxx +6 -3
  145. data/ext/couchbase/core/metrics/logging_meter.cxx +5 -10
  146. data/ext/couchbase/core/metrics/meter_wrapper.cxx +63 -33
  147. data/ext/couchbase/core/metrics/meter_wrapper.hxx +55 -3
  148. data/ext/couchbase/core/operations/analytics_response_parsing.cxx +177 -0
  149. data/ext/couchbase/core/operations/analytics_response_parsing.hxx +55 -0
  150. data/ext/couchbase/core/operations/document_analytics.cxx +48 -130
  151. data/ext/couchbase/core/operations/document_analytics.hxx +14 -2
  152. data/ext/couchbase/core/operations/document_get_all_replicas.hxx +15 -7
  153. data/ext/couchbase/core/operations/document_get_any_replica.hxx +19 -8
  154. data/ext/couchbase/core/operations/document_get_projected.cxx +1 -1
  155. data/ext/couchbase/core/{impl/get_replica.cxx → operations/document_get_replica.cxx} +21 -3
  156. data/ext/couchbase/core/{impl/get_replica.hxx → operations/document_get_replica.hxx} +31 -4
  157. data/ext/couchbase/core/operations/document_lookup_in.cxx +10 -10
  158. data/ext/couchbase/core/operations/document_lookup_in_all_replicas.hxx +228 -198
  159. data/ext/couchbase/core/operations/document_lookup_in_any_replica.hxx +215 -217
  160. data/ext/couchbase/core/operations/document_query.cxx +108 -221
  161. data/ext/couchbase/core/operations/document_query.hxx +18 -4
  162. data/ext/couchbase/core/operations/document_search.cxx +52 -8
  163. data/ext/couchbase/core/operations/document_search.hxx +3 -0
  164. data/ext/couchbase/core/operations/document_view.cxx +3 -3
  165. data/ext/couchbase/core/operations/management/analytics_dataset_create.cxx +15 -8
  166. data/ext/couchbase/core/operations/management/analytics_dataset_create.hxx +4 -0
  167. data/ext/couchbase/core/operations/management/analytics_dataset_drop.cxx +11 -6
  168. data/ext/couchbase/core/operations/management/analytics_dataset_get_all.cxx +3 -2
  169. data/ext/couchbase/core/operations/management/analytics_dataverse_create.cxx +10 -5
  170. data/ext/couchbase/core/operations/management/analytics_dataverse_drop.cxx +10 -5
  171. data/ext/couchbase/core/operations/management/analytics_get_pending_mutations.cxx +2 -1
  172. data/ext/couchbase/core/operations/management/analytics_index_create.cxx +33 -8
  173. data/ext/couchbase/core/operations/management/analytics_index_drop.cxx +12 -7
  174. data/ext/couchbase/core/operations/management/analytics_index_get_all.cxx +3 -2
  175. data/ext/couchbase/core/operations/management/analytics_link_connect.cxx +11 -6
  176. data/ext/couchbase/core/operations/management/analytics_link_create.cxx +3 -3
  177. data/ext/couchbase/core/operations/management/analytics_link_disconnect.cxx +10 -4
  178. data/ext/couchbase/core/operations/management/analytics_link_drop.cxx +6 -4
  179. data/ext/couchbase/core/operations/management/analytics_link_get_all.cxx +4 -3
  180. data/ext/couchbase/core/operations/management/analytics_link_replace.cxx +3 -3
  181. data/ext/couchbase/core/operations/management/analytics_link_utils.hxx +1 -1
  182. data/ext/couchbase/core/operations/management/bucket_create.cxx +37 -38
  183. data/ext/couchbase/core/operations/management/bucket_update.cxx +26 -28
  184. data/ext/couchbase/core/operations/management/change_password.cxx +2 -0
  185. data/ext/couchbase/core/operations/management/collection_drop.cxx +5 -1
  186. data/ext/couchbase/core/operations/management/error_utils.cxx +59 -5
  187. data/ext/couchbase/core/operations/management/error_utils.hxx +29 -1
  188. data/ext/couchbase/core/operations/management/eventing_deploy_function.cxx +9 -3
  189. data/ext/couchbase/core/operations/management/eventing_drop_function.cxx +8 -3
  190. data/ext/couchbase/core/operations/management/eventing_get_all_functions.cxx +4 -2
  191. data/ext/couchbase/core/operations/management/eventing_get_function.cxx +5 -3
  192. data/ext/couchbase/core/operations/management/eventing_get_status.cxx +4 -2
  193. data/ext/couchbase/core/operations/management/eventing_pause_function.cxx +9 -3
  194. data/ext/couchbase/core/operations/management/eventing_resume_function.cxx +9 -3
  195. data/ext/couchbase/core/operations/management/eventing_undeploy_function.cxx +9 -3
  196. data/ext/couchbase/core/operations/management/eventing_upsert_function.cxx +9 -3
  197. data/ext/couchbase/core/operations/management/group_drop.cxx +3 -1
  198. data/ext/couchbase/core/operations/management/group_get.cxx +3 -1
  199. data/ext/couchbase/core/operations/management/group_upsert.cxx +11 -8
  200. data/ext/couchbase/core/operations/management/query_index_build.cxx +5 -2
  201. data/ext/couchbase/core/operations/management/query_index_create.cxx +9 -3
  202. data/ext/couchbase/core/operations/management/query_index_drop.cxx +11 -4
  203. data/ext/couchbase/core/operations/management/query_index_get_all.cxx +1 -1
  204. data/ext/couchbase/core/operations/management/query_index_get_all_deferred.cxx +1 -1
  205. data/ext/couchbase/core/operations/management/scope_drop.cxx +5 -1
  206. data/ext/couchbase/core/operations/management/scope_get_all.cxx +6 -1
  207. data/ext/couchbase/core/operations/management/search_get_stats.cxx +10 -0
  208. data/ext/couchbase/core/operations/management/search_index_analyze_document.cxx +3 -2
  209. data/ext/couchbase/core/operations/management/search_index_control_ingest.cxx +4 -3
  210. data/ext/couchbase/core/operations/management/search_index_control_plan_freeze.cxx +4 -3
  211. data/ext/couchbase/core/operations/management/search_index_control_query.cxx +4 -3
  212. data/ext/couchbase/core/operations/management/search_index_drop.cxx +2 -2
  213. data/ext/couchbase/core/operations/management/search_index_get.cxx +2 -2
  214. data/ext/couchbase/core/operations/management/search_index_get_documents_count.cxx +3 -2
  215. data/ext/couchbase/core/operations/management/search_index_get_stats.cxx +3 -1
  216. data/ext/couchbase/core/operations/management/search_index_upsert.cxx +2 -2
  217. data/ext/couchbase/core/operations/management/user_drop.cxx +3 -1
  218. data/ext/couchbase/core/operations/management/user_get.cxx +3 -1
  219. data/ext/couchbase/core/operations/management/user_upsert.cxx +12 -9
  220. data/ext/couchbase/core/operations/management/view_index_drop.cxx +1 -1
  221. data/ext/couchbase/core/operations/management/view_index_get.cxx +1 -1
  222. data/ext/couchbase/core/operations/management/view_index_upsert.cxx +1 -1
  223. data/ext/couchbase/core/operations/operation_traits.hxx +18 -0
  224. data/ext/couchbase/core/operations/query_response_parsing.cxx +211 -0
  225. data/ext/couchbase/core/operations/query_response_parsing.hxx +60 -0
  226. data/ext/couchbase/core/operations_fwd.hxx +3 -3
  227. data/ext/couchbase/core/origin.cxx +54 -26
  228. data/ext/couchbase/core/origin.hxx +12 -0
  229. data/ext/couchbase/core/orphan_reporter.cxx +2 -1
  230. data/ext/couchbase/core/platform/random.cc +7 -7
  231. data/ext/couchbase/core/protocol/client_request.cxx +2 -2
  232. data/ext/couchbase/core/protocol/client_request.hxx +38 -21
  233. data/ext/couchbase/core/protocol/client_response.cxx +11 -3
  234. data/ext/couchbase/core/protocol/client_response.hxx +27 -6
  235. data/ext/couchbase/core/protocol/cmd_append.cxx +2 -2
  236. data/ext/couchbase/core/protocol/cmd_cluster_map_change_notification.cxx +2 -2
  237. data/ext/couchbase/core/protocol/cmd_decrement.cxx +6 -6
  238. data/ext/couchbase/core/protocol/cmd_get.cxx +1 -1
  239. data/ext/couchbase/core/protocol/cmd_get_and_lock.cxx +2 -2
  240. data/ext/couchbase/core/protocol/cmd_get_and_touch.cxx +2 -2
  241. data/ext/couchbase/core/protocol/cmd_get_cluster_config.cxx +6 -4
  242. data/ext/couchbase/core/protocol/cmd_get_collection_id.cxx +2 -2
  243. data/ext/couchbase/core/protocol/cmd_get_error_map.cxx +1 -1
  244. data/ext/couchbase/core/protocol/cmd_get_meta.cxx +4 -4
  245. data/ext/couchbase/core/protocol/cmd_get_replica.cxx +1 -1
  246. data/ext/couchbase/core/protocol/cmd_hello.cxx +1 -1
  247. data/ext/couchbase/core/protocol/cmd_increment.cxx +6 -6
  248. data/ext/couchbase/core/protocol/cmd_insert.cxx +4 -4
  249. data/ext/couchbase/core/protocol/cmd_lookup_in.cxx +4 -3
  250. data/ext/couchbase/core/protocol/cmd_lookup_in_replica.cxx +4 -3
  251. data/ext/couchbase/core/protocol/cmd_mutate_in.cxx +10 -9
  252. data/ext/couchbase/core/protocol/cmd_mutate_in.hxx +10 -10
  253. data/ext/couchbase/core/protocol/cmd_observe_seqno.cxx +7 -7
  254. data/ext/couchbase/core/protocol/cmd_prepend.cxx +2 -2
  255. data/ext/couchbase/core/protocol/cmd_remove.cxx +2 -2
  256. data/ext/couchbase/core/protocol/cmd_replace.cxx +4 -4
  257. data/ext/couchbase/core/protocol/cmd_touch.cxx +1 -1
  258. data/ext/couchbase/core/protocol/cmd_upsert.cxx +4 -4
  259. data/ext/couchbase/core/protocol/datatype.hxx +118 -1
  260. data/ext/couchbase/core/protocol/frame_info_utils.cxx +1 -1
  261. data/ext/couchbase/core/protocol/magic.hxx +4 -0
  262. data/ext/couchbase/core/protocol/magic_fmt.hxx +3 -0
  263. data/ext/couchbase/core/protocol/server_request.hxx +3 -1
  264. data/ext/couchbase/core/protocol/status.cxx +1 -1
  265. data/ext/couchbase/core/protostellar/analytics_converter.hxx +171 -0
  266. data/ext/couchbase/core/protostellar/bucket_admin_converter.hxx +301 -0
  267. data/ext/couchbase/core/protostellar/collection_admin_converter.hxx +92 -0
  268. data/ext/couchbase/core/protostellar/component.cxx +2445 -0
  269. data/ext/couchbase/core/protostellar/component.hxx +391 -0
  270. data/ext/couchbase/core/{columnar/bootstrap_notification_subscriber.hxx → protostellar/compression_settings.hxx} +11 -10
  271. data/ext/couchbase/core/protostellar/credentials.cxx +233 -0
  272. data/ext/couchbase/core/protostellar/credentials.hxx +88 -0
  273. data/ext/couchbase/core/protostellar/dispatcher.cxx +57 -0
  274. data/ext/couchbase/core/protostellar/dispatcher.hxx +482 -0
  275. data/ext/couchbase/core/protostellar/error_utils.cxx +293 -0
  276. data/ext/couchbase/core/protostellar/error_utils.hxx +97 -0
  277. data/ext/couchbase/core/protostellar/json_payload.hxx +49 -0
  278. data/ext/couchbase/core/protostellar/kv_converter.cxx +70 -0
  279. data/ext/couchbase/core/protostellar/kv_converter.hxx +600 -0
  280. data/ext/couchbase/core/protostellar/query_converter.hxx +265 -0
  281. data/ext/couchbase/core/protostellar/query_index_admin_converter.hxx +291 -0
  282. data/ext/couchbase/core/protostellar/query_proto.hxx +73 -0
  283. data/ext/couchbase/core/protostellar/search_converter.hxx +244 -0
  284. data/ext/couchbase/core/protostellar/search_index_admin_converter.hxx +156 -0
  285. data/ext/couchbase/core/protostellar/view_converter.hxx +158 -0
  286. data/ext/couchbase/core/query_stream.cxx +447 -0
  287. data/ext/couchbase/core/query_stream.hxx +141 -0
  288. data/ext/couchbase/core/query_stream_component.cxx +243 -0
  289. data/ext/couchbase/core/query_stream_component.hxx +68 -0
  290. data/ext/couchbase/core/range_scan_load_balancer.cxx +15 -8
  291. data/ext/couchbase/core/range_scan_orchestrator.cxx +15 -3
  292. data/ext/couchbase/core/response_handler.hxx +41 -4
  293. data/ext/couchbase/core/row_streamer.cxx +332 -46
  294. data/ext/couchbase/core/row_streamer.hxx +31 -1
  295. data/ext/couchbase/core/row_streamer_options.hxx +55 -0
  296. data/ext/couchbase/core/sasl/client.h +3 -0
  297. data/ext/couchbase/core/search_scoring.hxx +66 -0
  298. data/ext/couchbase/core/stream_error_details.hxx +69 -0
  299. data/ext/couchbase/core/timeout_defaults.hxx +0 -1
  300. data/ext/couchbase/core/topology/capabilities.hxx +6 -0
  301. data/ext/couchbase/core/topology/capabilities_fmt.hxx +3 -0
  302. data/ext/couchbase/core/topology/configuration.cxx +21 -6
  303. data/ext/couchbase/core/topology/configuration.hxx +29 -8
  304. data/ext/couchbase/core/topology/configuration_json.hxx +3 -0
  305. data/ext/couchbase/core/topology/error_map_json.hxx +3 -1
  306. data/ext/couchbase/core/tracing/threshold_logging_tracer.cxx +70 -4
  307. data/ext/couchbase/core/tracing/threshold_logging_tracer.hxx +10 -0
  308. data/ext/couchbase/core/transactions/active_transaction_record.cxx +0 -34
  309. data/ext/couchbase/core/transactions/attempt_context_impl.cxx +362 -241
  310. data/ext/couchbase/core/transactions/attempt_context_impl.hxx +24 -3
  311. data/ext/couchbase/core/transactions/error_list.hxx +2 -2
  312. data/ext/couchbase/core/transactions/get_multi_fetch.hxx +160 -0
  313. data/ext/couchbase/core/transactions/get_multi_orchestrator.cxx +326 -125
  314. data/ext/couchbase/core/transactions/get_multi_transaction_id.hxx +62 -0
  315. data/ext/couchbase/core/transactions/internal/atr_entry.hxx +22 -2
  316. data/ext/couchbase/core/transactions/internal/transaction_context.hxx +2 -2
  317. data/ext/couchbase/core/transactions/internal/transactions_cleanup.hxx +23 -1
  318. data/ext/couchbase/core/transactions/internal/utils.hxx +50 -14
  319. data/ext/couchbase/core/transactions/staged_mutation.cxx +23 -18
  320. data/ext/couchbase/core/transactions/staged_mutation.hxx +15 -4
  321. data/ext/couchbase/core/transactions/transaction_context.cxx +55 -40
  322. data/ext/couchbase/core/transactions/transaction_get_result.hxx +16 -0
  323. data/ext/couchbase/core/transactions/transactions.cxx +3 -55
  324. data/ext/couchbase/core/transactions/transactions_cleanup.cxx +194 -66
  325. data/ext/couchbase/core/transactions/transactions_config.cxx +0 -11
  326. data/ext/couchbase/core/transactions/utils.cxx +10 -0
  327. data/ext/couchbase/core/transactions/waitable_op_list.hxx +15 -4
  328. data/ext/couchbase/core/transactions.hxx +9 -9
  329. data/ext/couchbase/core/utils/byteswap.hxx +166 -9
  330. data/ext/couchbase/core/utils/connection_string.cxx +50 -85
  331. data/ext/couchbase/core/utils/connection_string.hxx +15 -0
  332. data/ext/couchbase/core/{columnar/backoff_calculator.hxx → utils/deadline.hxx} +13 -13
  333. data/ext/couchbase/core/utils/duration_parser.cxx +1 -1
  334. data/ext/couchbase/core/utils/json_streaming_lexer.cxx +48 -5
  335. data/ext/couchbase/core/utils/json_streaming_lexer.hxx +4 -1
  336. data/ext/couchbase/core/utils/movable_function.hxx +301 -52
  337. data/ext/couchbase/core/utils/name_codec.hxx +154 -11
  338. data/ext/couchbase/core/{columnar/retry_info.hxx → utils/process_id.hxx} +18 -13
  339. data/ext/couchbase/core/utils/url_codec.cxx +12 -51
  340. data/ext/couchbase/core/websocket_codec.cxx +3 -29
  341. data/ext/couchbase/couchbase/analytics_options.hxx +21 -10
  342. data/ext/couchbase/couchbase/analytics_row.hxx +105 -0
  343. data/ext/couchbase/couchbase/analytics_stream_result.hxx +266 -0
  344. data/ext/couchbase/couchbase/boolean_query.hxx +3 -3
  345. data/ext/couchbase/couchbase/bucket.hxx +57 -0
  346. data/ext/couchbase/couchbase/cluster.hxx +216 -5
  347. data/ext/couchbase/{core/columnar/security_options.hxx → couchbase/cluster_state.hxx} +18 -14
  348. data/ext/couchbase/couchbase/codec/lenient_json_transcoder.hxx +69 -0
  349. data/ext/couchbase/couchbase/collection.hxx +234 -14
  350. data/ext/couchbase/couchbase/common_durability_options.hxx +18 -3
  351. data/ext/couchbase/couchbase/conjunction_query.hxx +1 -1
  352. data/ext/couchbase/couchbase/create_dataset_analytics_options.hxx +6 -1
  353. data/ext/couchbase/couchbase/date_range_query.hxx +2 -2
  354. data/ext/couchbase/couchbase/decrement_options.hxx +1 -1
  355. data/ext/couchbase/couchbase/disjunction_query.hxx +1 -1
  356. data/ext/couchbase/couchbase/error.hxx +32 -0
  357. data/ext/couchbase/couchbase/error_codes.hxx +32 -0
  358. data/ext/couchbase/couchbase/geo_bounding_box_query.hxx +1 -1
  359. data/ext/couchbase/couchbase/geo_distance_query.hxx +1 -1
  360. data/ext/couchbase/couchbase/geo_polygon_query.hxx +1 -1
  361. data/ext/couchbase/couchbase/get_replica_options.hxx +68 -0
  362. data/ext/couchbase/couchbase/get_replica_result.hxx +5 -3
  363. data/ext/couchbase/couchbase/get_replica_strategy.hxx +161 -0
  364. data/ext/couchbase/couchbase/get_result.hxx +1 -0
  365. data/ext/couchbase/couchbase/increment_options.hxx +1 -1
  366. data/ext/couchbase/couchbase/insert_options.hxx +1 -1
  367. data/ext/couchbase/couchbase/match_phrase_query.hxx +1 -1
  368. data/ext/couchbase/couchbase/match_query.hxx +1 -1
  369. data/ext/couchbase/couchbase/mutate_in_options.hxx +3 -3
  370. data/ext/couchbase/couchbase/network_options.hxx +1 -1
  371. data/ext/couchbase/couchbase/node_id.hxx +42 -14
  372. data/ext/couchbase/couchbase/node_id_for_options.hxx +7 -1
  373. data/ext/couchbase/couchbase/node_ids_options.hxx +8 -2
  374. data/ext/couchbase/couchbase/numeric_range_query.hxx +1 -1
  375. data/ext/couchbase/couchbase/phrase_query.hxx +1 -1
  376. data/ext/couchbase/couchbase/prefix_query.hxx +1 -1
  377. data/ext/couchbase/couchbase/query_options.hxx +23 -12
  378. data/ext/couchbase/couchbase/query_row.hxx +111 -0
  379. data/ext/couchbase/couchbase/query_stream_result.hxx +302 -0
  380. data/ext/couchbase/couchbase/query_string_query.hxx +3 -3
  381. data/ext/couchbase/couchbase/regexp_query.hxx +1 -1
  382. data/ext/couchbase/couchbase/replace_options.hxx +3 -3
  383. data/ext/couchbase/couchbase/result.hxx +14 -3
  384. data/ext/couchbase/couchbase/scan_result_item.hxx +1 -0
  385. data/ext/couchbase/couchbase/scope.hxx +85 -2
  386. data/ext/couchbase/couchbase/search_options.hxx +58 -8
  387. data/ext/couchbase/couchbase/search_scoring.hxx +94 -0
  388. data/ext/couchbase/couchbase/search_scoring_none.hxx +51 -0
  389. data/ext/couchbase/couchbase/search_scoring_reciprocal_rank_fusion.hxx +88 -0
  390. data/ext/couchbase/couchbase/search_scoring_relative_score_fusion.hxx +70 -0
  391. data/ext/couchbase/couchbase/term_range_query.hxx +1 -1
  392. data/ext/couchbase/couchbase/tracing/request_span.hxx +46 -0
  393. data/ext/couchbase/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_result.hxx +23 -21
  394. data/ext/couchbase/couchbase/transactions/transaction_get_multi_result.hxx +24 -19
  395. data/ext/couchbase/couchbase/transactions/transaction_get_result.hxx +17 -3
  396. data/ext/couchbase/couchbase/transactions/transactions_config.hxx +4 -2
  397. data/ext/couchbase/couchbase/transactions.hxx +27 -18
  398. data/ext/couchbase/couchbase/upsert_options.hxx +3 -3
  399. data/ext/couchbase/couchbase/vector_query.hxx +1 -1
  400. data/ext/couchbase/couchbase/wait_until_ready_options.hxx +111 -0
  401. data/ext/couchbase/couchbase/wildcard_query.hxx +1 -1
  402. data/ext/extconf.rb +15 -10
  403. data/ext/rcb_analytics.cxx +82 -102
  404. data/ext/rcb_backend.cxx +42 -36
  405. data/ext/rcb_backend.hxx +2 -0
  406. data/ext/rcb_buckets.cxx +47 -59
  407. data/ext/rcb_collections.cxx +42 -52
  408. data/ext/rcb_crud.cxx +345 -224
  409. data/ext/rcb_diagnostics.cxx +117 -27
  410. data/ext/rcb_exceptions.cxx +152 -99
  411. data/ext/rcb_exceptions.hxx +25 -0
  412. data/ext/rcb_extras.cxx +18 -19
  413. data/ext/rcb_hdr_histogram.cxx +19 -4
  414. data/ext/rcb_logger.cxx +202 -27
  415. data/ext/rcb_logger.hxx +12 -0
  416. data/ext/rcb_multi.cxx +40 -40
  417. data/ext/rcb_observability.cxx +7 -5
  418. data/ext/rcb_query.cxx +99 -142
  419. data/ext/rcb_range_scan.cxx +41 -35
  420. data/ext/rcb_search.cxx +98 -99
  421. data/ext/rcb_users.cxx +36 -56
  422. data/ext/rcb_utils.cxx +100 -37
  423. data/ext/rcb_utils.hxx +287 -33
  424. data/ext/rcb_version.cxx +1 -1
  425. data/ext/rcb_views.cxx +55 -51
  426. data/lib/active_support/cache/couchbase_store.rb +77 -12
  427. data/lib/couchbase/bucket.rb +20 -0
  428. data/lib/couchbase/cluster.rb +33 -14
  429. data/lib/couchbase/collection.rb +36 -1
  430. data/lib/couchbase/errors.rb +18 -0
  431. data/lib/couchbase/fork_hooks.rb +16 -7
  432. data/lib/couchbase/get_replica_strategy.rb +89 -0
  433. data/lib/couchbase/logger.rb +1 -1
  434. data/lib/couchbase/management/analytics_index_manager.rb +15 -15
  435. data/lib/couchbase/management/bucket_manager.rb +6 -6
  436. data/lib/couchbase/management/collection_manager.rb +6 -6
  437. data/lib/couchbase/management/query_index_manager.rb +7 -7
  438. data/lib/couchbase/management/search_index_manager.rb +13 -13
  439. data/lib/couchbase/management/user_manager.rb +10 -10
  440. data/lib/couchbase/management/view_index_manager.rb +5 -5
  441. data/lib/couchbase/options.rb +181 -44
  442. data/lib/couchbase/protostellar/bucket.rb +4 -0
  443. data/lib/couchbase/protostellar/cluster.rb +4 -0
  444. data/lib/couchbase/protostellar/collection.rb +4 -0
  445. data/lib/couchbase/protostellar/error_handling.rb +2 -1
  446. data/lib/couchbase/protostellar/request.rb +1 -0
  447. data/lib/couchbase/protostellar/request_generator/kv.rb +16 -2
  448. data/lib/couchbase/protostellar/request_generator/search.rb +17 -1
  449. data/lib/couchbase/protostellar/retry/orchestrator.rb +3 -2
  450. data/lib/couchbase/protostellar/retry/strategies/fail_fast.rb +33 -0
  451. data/lib/couchbase/protostellar/retry/strategies.rb +1 -0
  452. data/lib/couchbase/search_options.rb +95 -6
  453. data/lib/couchbase/version.rb +1 -1
  454. metadata +92 -33
  455. data/ext/0001-fix-build-for-mingw-w64-ucrt-x86_64-toolchain.patch +0 -40
  456. data/ext/couchbase/core/columnar/agent.hxx +0 -71
  457. data/ext/couchbase/core/columnar/agent_config.hxx +0 -59
  458. data/ext/couchbase/core/columnar/background_bootstrap_listener.hxx +0 -42
  459. data/ext/couchbase/core/columnar/backoff_calculator.cxx +0 -62
  460. data/ext/couchbase/core/columnar/columnar_agent.cxx +0 -147
  461. data/ext/couchbase/core/columnar/columnar_agent_config.cxx +0 -48
  462. data/ext/couchbase/core/columnar/columnar_error.cxx +0 -51
  463. data/ext/couchbase/core/columnar/database_management_options.hxx +0 -56
  464. data/ext/couchbase/core/columnar/error.hxx +0 -51
  465. data/ext/couchbase/core/columnar/error_codes.cxx +0 -107
  466. data/ext/couchbase/core/columnar/error_codes.hxx +0 -75
  467. data/ext/couchbase/core/columnar/management_component.cxx +0 -300
  468. data/ext/couchbase/core/columnar/management_component.hxx +0 -56
  469. data/ext/couchbase/core/columnar/query_component.cxx +0 -523
  470. data/ext/couchbase/core/columnar/query_component.hxx +0 -57
  471. data/ext/couchbase/core/columnar/query_options.hxx +0 -59
  472. data/ext/couchbase/core/columnar/query_result.cxx +0 -130
  473. data/ext/couchbase/core/columnar/query_result.hxx +0 -78
  474. data/ext/couchbase/core/io/config_tracker.cxx +0 -687
  475. data/ext/couchbase/core/io/config_tracker.hxx +0 -123
@@ -18,17 +18,247 @@
18
18
  #include "streams.hxx"
19
19
 
20
20
  #include "core/platform/uuid.h"
21
+ #include "core/utils/process_id.hxx"
21
22
 
22
23
  #include <asio.hpp>
23
24
  #include <asio/error.hpp>
24
25
  #include <asio/ssl.hpp>
26
+ #include <asio/ssl/host_name_verification.hpp>
27
+ // ERR_get_error()/SSL_* come from the OpenSSL or BoringSSL headers pulled in by
28
+ // <asio/ssl.hpp>; do not include <openssl/*.h> directly so the static BoringSSL
29
+ // build (which ships its own headers) keeps working, matching core/io/mcbp_session.cxx.
30
+
31
+ #include <algorithm>
32
+ #include <cerrno>
33
+ #include <memory>
34
+ #include <mutex>
35
+
36
+ #if !defined(_WIN32)
37
+ #include <unistd.h>
38
+ #endif
25
39
 
26
40
  namespace couchbase::core::io
27
41
  {
42
+ namespace
43
+ {
44
+ /**
45
+ * Release a socket this process inherited across fork(2), without disturbing the
46
+ * connection the parent is still using.
47
+ *
48
+ * Two things differ from the ordinary close path, and both matter:
49
+ *
50
+ * * No shutdown(2). It acts on the open file description, which fork(2) shares,
51
+ * so it would send FIN and break the parent's connection. close(2) drops only
52
+ * our own descriptor; the peer sees nothing.
53
+ * * release() before closing, rather than asio's close(). asio's close() skips
54
+ * EPOLL_CTL_DEL on the assumption that closing a descriptor removes its epoll
55
+ * registration. Per epoll(7) that happens only once *every* descriptor
56
+ * referring to the description is closed -- and the parent still holds one --
57
+ * so the registration would outlive the descriptor_state asio recycles, which
58
+ * is precisely the stale-pointer shape the reactor faults on. release() emits
59
+ * the EPOLL_CTL_DEL explicitly and hands back the raw descriptor to close.
60
+ *
61
+ * release() also completes any outstanding asynchronous operations on this socket
62
+ * with operation_aborted, which is what we want: those reads belong to the parent.
63
+ */
64
+ // Takes the basic_socket base rather than a concrete socket type, so the same code serves the
65
+ // plain socket, a TLS stream's lowest_layer(), whose type is that base, and the DNS client's
66
+ // sockets.
67
+ template<typename Protocol>
68
+ void
69
+ detach_inherited_socket(asio::basic_socket<Protocol>& socket, asio::error_code& ec)
70
+ {
71
+ if (!socket.is_open()) {
72
+ ec = asio::error::bad_descriptor;
73
+ return;
74
+ }
75
+
76
+ const auto handle = socket.release(ec);
77
+ if (ec) {
78
+ // Report it. Do not reach for asio's close() here: that is the very call
79
+ // whose epoll behaviour makes it unsafe on an inherited descriptor, and the
80
+ // descriptor goes away when this process exits anyway.
81
+ return;
82
+ }
83
+
84
+ #if defined(_WIN32)
85
+ // Unreachable: without fork(2) no socket is ever inherited, so the owner pid
86
+ // always matches and close() never takes this path.
87
+ (void)handle;
88
+ ec = asio::error::operation_not_supported;
89
+ #else
90
+ if (::close(handle) != 0) {
91
+ ec.assign(errno, asio::error::get_system_category());
92
+ }
93
+ #endif
94
+ }
95
+ } // namespace
96
+
97
+ auto
98
+ configure_tls_handshake(asio::ssl::stream<asio::ip::tcp::socket>& stream,
99
+ const std::string& hostname) -> std::error_code
100
+ {
101
+ if (hostname.empty()) {
102
+ // Fail closed: a TLS client connection must have a hostname to verify the
103
+ // server's identity against. Returning success here would silently fall back
104
+ // to CA-only validation -- the CWE-297 problem this function exists to prevent.
105
+ return asio::error::make_error_code(asio::error::invalid_argument);
106
+ }
107
+
108
+ // Send SNI so that name-based / multi-tenant TLS endpoints present the correct
109
+ // certificate for this hostname.
110
+ {
111
+ // SSL_set_tlsext_host_name() is a macro that expands to an internal C-style
112
+ // cast to void*, which trips -Wold-style-cast; suppress it locally.
113
+ #if defined(__GNUC__) || defined(__clang__)
114
+ #pragma GCC diagnostic push
115
+ #pragma GCC diagnostic ignored "-Wold-style-cast"
116
+ #endif
117
+ ERR_clear_error();
118
+ const auto sni_result = SSL_set_tlsext_host_name(stream.native_handle(), hostname.c_str());
119
+ #if defined(__GNUC__) || defined(__clang__)
120
+ #pragma GCC diagnostic pop
121
+ #endif
122
+ if (sni_result != 1) {
123
+ // Prefer the underlying SSL reason (if any) so failures are diagnosable,
124
+ // and fall back to a generic error when the SSL error queue is empty.
125
+ if (const auto reason = ERR_get_error(); reason != 0) {
126
+ return { static_cast<int>(reason), asio::error::get_ssl_category() };
127
+ }
128
+ return asio::error::make_error_code(asio::error::invalid_argument);
129
+ }
130
+ }
131
+
132
+ // Verify that the presented certificate actually identifies `hostname`
133
+ // (SAN/CN). asio::ssl::verify_peer on its own only validates the certificate
134
+ // chain, not the name. In verify_none mode OpenSSL ignores the callback
135
+ // result, so this has no effect there.
136
+ std::error_code ec{};
137
+ stream.set_verify_callback(asio::ssl::host_name_verification(hostname), ec);
138
+ return ec;
139
+ }
140
+
141
+ // The sockets of one io_context, each with the process that opened it. Held weakly: a socket also
142
+ // lives in the handlers that use it, such as a close posted before a fork, and is found there too.
143
+ class inherited_stream_registry : public asio::execution_context::service
144
+ {
145
+ public:
146
+ static inline asio::execution_context::id id{};
147
+
148
+ explicit inherited_stream_registry(asio::execution_context& ctx)
149
+ : asio::execution_context::service(ctx)
150
+ {
151
+ }
152
+
153
+ void add(const std::shared_ptr<asio::ip::tcp::socket>& socket)
154
+ {
155
+ const std::scoped_lock lock(mutex_);
156
+ prune();
157
+ plain_.push_back({ socket, utils::current_process_id() });
158
+ }
159
+
160
+ void add(const std::shared_ptr<asio::ssl::stream<asio::ip::tcp::socket>>& stream)
161
+ {
162
+ const std::scoped_lock lock(mutex_);
163
+ prune();
164
+ tls_.push_back({ stream, utils::current_process_id() });
165
+ }
166
+
167
+ void add(const std::shared_ptr<asio::ip::udp::socket>& socket)
168
+ {
169
+ const std::scoped_lock lock(mutex_);
170
+ prune();
171
+ udp_.push_back({ socket, utils::current_process_id() });
172
+ }
173
+
174
+ // The detach result needs no handling. asio's reactive release() fails only on a socket that is
175
+ // not open, which is skipped here, and once it succeeds the socket is out of the reactor with its
176
+ // operations aborted, so a failed close(2) cannot let anything run on the parent's connection.
177
+ void detach_inherited()
178
+ {
179
+ const auto pid = utils::current_process_id();
180
+ const std::scoped_lock lock(mutex_);
181
+ for (const auto& [weak, owner_pid] : plain_) {
182
+ if (auto socket = weak.lock(); socket && owner_pid != pid && socket->is_open()) {
183
+ asio::error_code ec{};
184
+ detach_inherited_socket(*socket, ec);
185
+ }
186
+ }
187
+ for (const auto& [weak, owner_pid] : tls_) {
188
+ if (auto stream = weak.lock();
189
+ stream && owner_pid != pid && stream->lowest_layer().is_open()) {
190
+ asio::error_code ec{};
191
+ detach_inherited_socket(stream->lowest_layer(), ec);
192
+ }
193
+ }
194
+ for (const auto& [weak, owner_pid] : udp_) {
195
+ if (auto socket = weak.lock(); socket && owner_pid != pid && socket->is_open()) {
196
+ asio::error_code ec{};
197
+ detach_inherited_socket(*socket, ec);
198
+ }
199
+ }
200
+ prune();
201
+ }
202
+
203
+ private:
204
+ template<typename Stream>
205
+ struct entry {
206
+ std::weak_ptr<Stream> stream;
207
+ std::int64_t owner_pid;
208
+ };
209
+
210
+ void shutdown() override
211
+ {
212
+ }
213
+
214
+ void prune()
215
+ {
216
+ const auto expired = [](const auto& e) {
217
+ return e.stream.expired();
218
+ };
219
+ plain_.erase(std::remove_if(plain_.begin(), plain_.end(), expired), plain_.end());
220
+ tls_.erase(std::remove_if(tls_.begin(), tls_.end(), expired), tls_.end());
221
+ udp_.erase(std::remove_if(udp_.begin(), udp_.end(), expired), udp_.end());
222
+ }
223
+
224
+ std::mutex mutex_{};
225
+ std::vector<entry<asio::ip::tcp::socket>> plain_{};
226
+ std::vector<entry<asio::ssl::stream<asio::ip::tcp::socket>>> tls_{};
227
+ std::vector<entry<asio::ip::udp::socket>> udp_{};
228
+ };
229
+
230
+ void
231
+ detach_inherited_sockets(asio::io_context& ctx)
232
+ {
233
+ if (asio::has_service<inherited_stream_registry>(ctx)) {
234
+ asio::use_service<inherited_stream_registry>(static_cast<asio::execution_context&>(ctx))
235
+ .detach_inherited();
236
+ }
237
+ }
238
+
239
+ void
240
+ register_inherited_socket(asio::io_context& ctx,
241
+ const std::shared_ptr<asio::ip::tcp::socket>& socket)
242
+ {
243
+ asio::use_service<inherited_stream_registry>(static_cast<asio::execution_context&>(ctx))
244
+ .add(socket);
245
+ }
246
+
247
+ void
248
+ register_inherited_socket(asio::io_context& ctx,
249
+ const std::shared_ptr<asio::ip::udp::socket>& socket)
250
+ {
251
+ asio::use_service<inherited_stream_registry>(static_cast<asio::execution_context&>(ctx))
252
+ .add(socket);
253
+ }
254
+
28
255
  stream_impl::stream_impl(asio::io_context& ctx, bool is_tls)
29
- : strand_(asio::make_strand(ctx))
256
+ : inherited_(
257
+ asio::use_service<inherited_stream_registry>(static_cast<asio::execution_context&>(ctx)))
258
+ , strand_(asio::make_strand(ctx))
30
259
  , tls_(is_tls)
31
260
  , id_(uuid::to_string(uuid::random()))
261
+ , owner_pid_(utils::current_process_id())
32
262
  {
33
263
  }
34
264
 
@@ -48,6 +278,7 @@ plain_stream_impl::plain_stream_impl(asio::io_context& ctx)
48
278
  : stream_impl(ctx, false)
49
279
  , stream_(std::make_shared<asio::ip::tcp::socket>(strand_))
50
280
  {
281
+ inherited_.add(stream_);
51
282
  }
52
283
 
53
284
  auto
@@ -79,12 +310,20 @@ plain_stream_impl::close(utils::movable_function<void(std::error_code)>&& handle
79
310
  if (!stream_) {
80
311
  return handler(asio::error::bad_descriptor);
81
312
  }
82
- return asio::post(strand_, [stream = std::move(stream_), handler = std::move(handler)]() {
83
- asio::error_code ec{};
84
- stream->shutdown(asio::socket_base::shutdown_both, ec);
85
- stream->close(ec);
86
- handler(ec);
87
- });
313
+ return asio::post(
314
+ strand_, [stream = std::move(stream_), owner_pid = owner_pid_, handler = std::move(handler)]() {
315
+ asio::error_code ec{};
316
+ // Decide here rather than when this was posted: io_.stop() at fork_prepare
317
+ // does not drain the queue, so a close queued before the fork runs in the
318
+ // child once the io_context is restarted, and there the answer differs.
319
+ if (owner_pid != utils::current_process_id()) {
320
+ detach_inherited_socket(*stream, ec);
321
+ } else {
322
+ stream->shutdown(asio::socket_base::shutdown_both, ec);
323
+ stream->close(ec);
324
+ }
325
+ handler(ec);
326
+ });
88
327
  }
89
328
 
90
329
  void
@@ -101,11 +340,13 @@ plain_stream_impl::set_options()
101
340
  void
102
341
  plain_stream_impl::async_connect(
103
342
  const asio::ip::tcp::resolver::results_type::endpoint_type& endpoint,
343
+ const std::string& /* hostname */,
104
344
  utils::movable_function<void(std::error_code)>&& handler)
105
345
  {
106
346
  if (!stream_) {
107
347
  id_ = uuid::to_string(uuid::random());
108
348
  stream_ = std::make_shared<asio::ip::tcp::socket>(strand_);
349
+ inherited_.add(stream_);
109
350
  }
110
351
  return stream_->async_connect(endpoint,
111
352
  [stream = stream_, handler = std::move(handler)](auto ec) {
@@ -147,6 +388,7 @@ tls_stream_impl::tls_stream_impl(asio::io_context& ctx, tls_context_provider& tl
147
388
  std::make_shared<asio::ssl::stream<asio::ip::tcp::socket>>(asio::ip::tcp::socket(strand_),
148
389
  *tls_.get_ctx()))
149
390
  {
391
+ inherited_.add(stream_);
150
392
  }
151
393
 
152
394
  auto
@@ -178,12 +420,19 @@ tls_stream_impl::close(utils::movable_function<void(std::error_code)>&& handler)
178
420
  if (!stream_) {
179
421
  return handler(asio::error::bad_descriptor);
180
422
  }
181
- return asio::post(strand_, [stream = std::move(stream_), handler = std::move(handler)]() {
182
- asio::error_code ec{};
183
- stream->lowest_layer().shutdown(asio::socket_base::shutdown_both, ec);
184
- stream->lowest_layer().close(ec);
185
- handler(ec);
186
- });
423
+ return asio::post(
424
+ strand_, [stream = std::move(stream_), owner_pid = owner_pid_, handler = std::move(handler)]() {
425
+ asio::error_code ec{};
426
+ // See plain_stream_impl::close(): the inheritance test belongs here, not at
427
+ // post time.
428
+ if (owner_pid != utils::current_process_id()) {
429
+ detach_inherited_socket(stream->lowest_layer(), ec);
430
+ } else {
431
+ stream->lowest_layer().shutdown(asio::socket_base::shutdown_both, ec);
432
+ stream->lowest_layer().close(ec);
433
+ }
434
+ handler(ec);
435
+ });
187
436
  }
188
437
 
189
438
  void
@@ -199,21 +448,33 @@ tls_stream_impl::set_options()
199
448
 
200
449
  void
201
450
  tls_stream_impl::async_connect(const asio::ip::tcp::resolver::results_type::endpoint_type& endpoint,
451
+ const std::string& hostname,
202
452
  utils::movable_function<void(std::error_code)>&& handler)
203
453
  {
204
454
  if (!stream_) {
205
455
  id_ = uuid::to_string(uuid::random());
206
456
  stream_ = std::make_shared<asio::ssl::stream<asio::ip::tcp::socket>>(
207
457
  asio::ip::tcp::socket(strand_), *tls_.get_ctx());
458
+ inherited_.add(stream_);
208
459
  }
209
460
  return stream_->lowest_layer().async_connect(
210
- endpoint, [stream = stream_, handler = std::move(handler)](std::error_code ec_connect) mutable {
461
+ endpoint,
462
+ // hostname is copied into the closure (owning std::string) so the async
463
+ // handler does not depend on the caller's argument lifetime.
464
+ [stream = stream_, hostname = hostname, handler = std::move(handler)](
465
+ std::error_code ec_connect) mutable {
211
466
  if (ec_connect == asio::error::operation_aborted) {
212
467
  return;
213
468
  }
214
469
  if (ec_connect) {
215
470
  return handler(ec_connect);
216
471
  }
472
+ // Verify the server's identity against `hostname` (SAN/CN) and set SNI
473
+ // before the handshake. Without this, verify_peer only checks that the
474
+ // certificate chains to a trusted CA, not that it identifies this host.
475
+ if (auto ec_identity = configure_tls_handshake(*stream, hostname); ec_identity) {
476
+ return handler(ec_identity);
477
+ }
217
478
  stream->async_handshake(
218
479
  asio::ssl::stream_base::client,
219
480
  [stream, handler = std::move(handler)](std::error_code ec_handshake) mutable {
@@ -24,10 +24,12 @@
24
24
  #include <asio/error.hpp>
25
25
  #include <asio/io_context.hpp>
26
26
  #include <asio/ip/tcp.hpp>
27
+ #include <asio/ip/udp.hpp>
27
28
  #include <asio/ssl/context.hpp>
28
29
  #include <asio/ssl/stream.hpp>
29
30
  #include <asio/strand.hpp>
30
31
 
32
+ #include <cstdint>
31
33
  #include <string>
32
34
  #include <vector>
33
35
 
@@ -59,12 +61,58 @@ async_resolve(ip_protocol protocol,
59
61
  return resolver.async_resolve(hostname, service, std::forward<Handler>(handler));
60
62
  }
61
63
 
64
+ /**
65
+ * Configure server-identity verification on a client TLS stream prior to the
66
+ * handshake: install a hostname (SAN/CN) verification callback and set the SNI
67
+ * server name. When the underlying context is in verify_none mode the callback
68
+ * has no effect, but SNI is still set.
69
+ *
70
+ * Without this, asio::ssl::verify_peer only validates that the certificate
71
+ * chains to a trusted CA -- it does NOT check that the certificate identifies
72
+ * the host being contacted (CWE-297).
73
+ *
74
+ * @param stream the client TLS stream that has connected but not yet handshaken
75
+ * @param hostname the canonical hostname the caller intended to reach; must not
76
+ * be empty (an empty hostname is rejected to avoid silently falling back to
77
+ * CA-only validation)
78
+ * @return a non-empty error_code if the hostname is empty, SNI could not be set,
79
+ * or the hostname-verification callback could not be installed; empty on success
80
+ */
81
+ [[nodiscard]] auto
82
+ configure_tls_handshake(asio::ssl::stream<asio::ip::tcp::socket>& stream,
83
+ const std::string& hostname) -> std::error_code;
84
+
85
+ class inherited_stream_registry;
86
+
87
+ // For a forked child, after io_context::notify_fork(fork_child) and before anything runs on ctx:
88
+ // releases and closes every registered stream and socket on ctx that another process opened,
89
+ // without shutdown(2). No pending operation on them can then read or write a connection the
90
+ // parent is still using. The DNS client registers its sockets through register_inherited_socket().
91
+ void
92
+ detach_inherited_sockets(asio::io_context& ctx);
93
+
94
+ // Records a socket on ctx that is not a stream, so that detach_inherited_sockets() in a forked
95
+ // child releases it. Held weakly: the registry does not keep the socket alive.
96
+ void
97
+ register_inherited_socket(asio::io_context& ctx,
98
+ const std::shared_ptr<asio::ip::tcp::socket>& socket);
99
+ void
100
+ register_inherited_socket(asio::io_context& ctx,
101
+ const std::shared_ptr<asio::ip::udp::socket>& socket);
102
+
62
103
  class stream_impl
63
104
  {
64
105
  protected:
106
+ inherited_stream_registry& inherited_;
65
107
  asio::strand<asio::io_context::executor_type> strand_;
66
108
  bool tls_;
67
109
  std::string id_{};
110
+ // Identifies the process that opened this socket. close() compares it against
111
+ // the process running at that moment to tell a socket inherited across fork(2)
112
+ // apart from one this process opened itself, because an inherited socket must not
113
+ // be shut down: shutdown(2) acts on the open file description, which fork(2)
114
+ // shares, so it would tear down a connection the parent is still using.
115
+ std::int64_t owner_pid_;
68
116
 
69
117
  public:
70
118
  stream_impl(asio::io_context& ctx, bool is_tls);
@@ -88,7 +136,10 @@ public:
88
136
 
89
137
  virtual void set_options() = 0;
90
138
 
139
+ // `hostname` is the canonical name the caller intends to reach; TLS streams use
140
+ // it to verify the server certificate's identity (SAN/CN) and to set SNI.
91
141
  virtual void async_connect(const asio::ip::tcp::resolver::results_type::endpoint_type& endpoint,
142
+ const std::string& hostname,
92
143
  utils::movable_function<void(std::error_code)>&& handler) = 0;
93
144
 
94
145
  virtual void async_write(
@@ -117,6 +168,7 @@ public:
117
168
  void set_options() override;
118
169
 
119
170
  void async_connect(const asio::ip::tcp::resolver::results_type::endpoint_type& endpoint,
171
+ const std::string& hostname,
120
172
  utils::movable_function<void(std::error_code)>&& handler) override;
121
173
 
122
174
  void async_write(std::vector<asio::const_buffer>& buffers,
@@ -145,6 +197,7 @@ public:
145
197
  void set_options() override;
146
198
 
147
199
  void async_connect(const asio::ip::tcp::resolver::results_type::endpoint_type& endpoint,
200
+ const std::string& hostname,
148
201
  utils::movable_function<void(std::error_code)>&& handler) override;
149
202
 
150
203
  void async_write(std::vector<asio::const_buffer>& buffers,
@@ -62,6 +62,10 @@ std::atomic_int file_logger_version{ 0 };
62
62
  std::shared_ptr<couchbase::core::logger::log_callback> log_callback{};
63
63
  std::mutex log_callback_mutex;
64
64
  std::atomic_int log_callback_version{ 0 };
65
+ // Mirrors "is log_callback set?" so the hot-path predicate can be answered with a single atomic
66
+ // load, without copying the owning shared_ptr. Updated under log_callback_mutex alongside
67
+ // log_callback.
68
+ std::atomic<bool> log_callback_present{ false };
65
69
 
66
70
  auto
67
71
  get_file_logger() -> std::shared_ptr<spdlog::logger>
@@ -94,6 +98,7 @@ update_callback_logger(const std::shared_ptr<couchbase::core::logger::log_callba
94
98
  {
95
99
  const std::scoped_lock lock(log_callback_mutex);
96
100
  log_callback = new_callback;
101
+ log_callback_present.store(new_callback != nullptr, std::memory_order_relaxed);
97
102
  ++log_callback_version;
98
103
  }
99
104
 
@@ -196,6 +201,14 @@ should_log(level lvl) -> bool
196
201
  return false;
197
202
  }
198
203
 
204
+ auto
205
+ has_custom_callback() -> bool
206
+ {
207
+ // A log macro asks this on every call; answer it with a single atomic load rather than copying
208
+ // the owning shared_ptr that get_custom_callback() returns.
209
+ return log_callback_present.load(std::memory_order_relaxed);
210
+ }
211
+
199
212
  namespace detail
200
213
  {
201
214
  void
@@ -252,6 +265,29 @@ is_initialized() -> bool
252
265
  return get_file_logger() != nullptr;
253
266
  }
254
267
 
268
+ namespace
269
+ {
270
+ std::atomic<bool> log_redaction_enabled{ false };
271
+ } // namespace
272
+
273
+ void
274
+ set_log_redaction(bool enable)
275
+ {
276
+ const auto was_enabled = log_redaction_enabled.exchange(enable, std::memory_order_relaxed);
277
+ if (enable && !was_enabled) {
278
+ CB_LOG_INFO_RAW(
279
+ "Log redaction enabled: sensitive values are wrapped in redaction tags. This log still "
280
+ "contains identifying information until those tags are processed. Once redacted, diagnosis "
281
+ "and support of issues may be challenging or not possible");
282
+ }
283
+ }
284
+
285
+ auto
286
+ is_log_redaction_enabled() -> bool
287
+ {
288
+ return log_redaction_enabled.load(std::memory_order_relaxed);
289
+ }
290
+
255
291
  auto
256
292
  create_file_logger_impl(const std::string& logger_name, const configuration& logger_settings)
257
293
  -> std::pair<std::optional<std::string>, std::shared_ptr<spdlog::logger>>
@@ -181,6 +181,14 @@ get_lowest_log_level() -> level;
181
181
  auto
182
182
  should_log(level lvl) -> bool;
183
183
 
184
+ /**
185
+ * @return true if a custom log callback is currently registered. Used to decide whether log
186
+ * arguments need to be formatted at all, independently of the severity level (the callback does
187
+ * its own level filtering).
188
+ */
189
+ auto
190
+ has_custom_callback() -> bool;
191
+
184
192
  auto
185
193
  should_log_protocol() -> bool;
186
194
 
@@ -265,6 +273,29 @@ shutdown();
265
273
  */
266
274
  auto
267
275
  is_initialized() -> bool;
276
+
277
+ /**
278
+ * Enable or disable log redaction.
279
+ *
280
+ * Redaction state is process-wide. When enabled, values wrapped by the helpers in
281
+ * `core/logger/redaction.hxx` are surrounded by `<ud>`/`<md>`/`<sd>` tags so that an external tool
282
+ * can strip or hash them; the SDK never removes or obscures anything itself.
283
+ *
284
+ * Connection-context log prefixes bake their tags in at construction, so redaction must be enabled
285
+ * *before* connecting for those prefixes to be tagged. Enabling it later affects only prefixes
286
+ * built afterwards.
287
+ *
288
+ * Enabling emits a one-time breadcrumb recording that the log carries redaction tags and still
289
+ * needs processing. That breadcrumb is dropped if no logger has been initialized yet.
290
+ */
291
+ void
292
+ set_log_redaction(bool enable);
293
+
294
+ /**
295
+ * @return whether log redaction is currently enabled
296
+ */
297
+ auto
298
+ is_log_redaction_enabled() -> bool;
268
299
  } // namespace couchbase::core::logger
269
300
 
270
301
  #if defined(__GNUC__) || defined(__clang__)
@@ -279,10 +310,17 @@ is_initialized() -> bool;
279
310
  */
280
311
  #define COUCHBASE_LOG(file, line, function, severity, ...) \
281
312
  do { \
282
- couchbase::core::logger::log_custom_logger(file, line, function, severity, __VA_ARGS__); \
283
- \
284
- if (couchbase::core::logger::should_log(severity)) { \
285
- couchbase::core::logger::log(file, line, function, severity, __VA_ARGS__); \
313
+ const bool cb_log_to_file_ = couchbase::core::logger::should_log(severity); \
314
+ const bool cb_log_to_custom_ = couchbase::core::logger::has_custom_callback(); \
315
+ if (cb_log_to_file_ || cb_log_to_custom_) { \
316
+ auto cb_log_message_ = ::fmt::format(__VA_ARGS__); \
317
+ if (cb_log_to_custom_) { \
318
+ couchbase::core::logger::detail::log_custom_logger( \
319
+ file, line, function, severity, cb_log_message_); \
320
+ } \
321
+ if (cb_log_to_file_) { \
322
+ couchbase::core::logger::detail::log(file, line, function, severity, cb_log_message_); \
323
+ } \
286
324
  } \
287
325
  } while (false)
288
326
 
@@ -343,8 +381,10 @@ is_initialized() -> bool;
343
381
  */
344
382
  #define COUCHBASE_LOG_RAW(file, line, function, severity, msg) \
345
383
  do { \
346
- couchbase::core::logger::log_custom_logger(file, line, function, severity, msg); \
347
- \
384
+ /* msg is already a string; deliver it directly (no fmt::format), gated like COUCHBASE_LOG. */ \
385
+ if (couchbase::core::logger::has_custom_callback()) { \
386
+ couchbase::core::logger::detail::log_custom_logger(file, line, function, severity, msg); \
387
+ } \
348
388
  if (couchbase::core::logger::should_log(severity)) { \
349
389
  couchbase::core::logger::detail::log(file, line, function, severity, msg); \
350
390
  } \