couchbase 3.5.6 → 3.6.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 (156) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/ext/cache/extconf_include.rb +3 -3
  4. data/ext/cache/mozilla-ca-bundle.crt +3 -165
  5. data/ext/cache/mozilla-ca-bundle.sha256 +1 -1
  6. data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/CMakeLists.txt +14 -10
  7. data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy.cc +7 -4
  8. data/ext/couchbase/CMakeLists.txt +12 -1
  9. data/ext/couchbase/cmake/Profiler.cmake +15 -0
  10. data/ext/couchbase/cmake/ThirdPartyDependencies.cmake +2 -2
  11. data/ext/couchbase/cmake/couchbase_cxx_client.pc.in +1 -1
  12. data/ext/couchbase/core/app_telemetry_address.cxx +55 -0
  13. data/ext/couchbase/core/app_telemetry_address.hxx +39 -0
  14. data/ext/couchbase/core/app_telemetry_meter.cxx +753 -0
  15. data/ext/couchbase/core/app_telemetry_meter.hxx +198 -0
  16. data/ext/couchbase/core/app_telemetry_reporter.cxx +895 -0
  17. data/ext/couchbase/core/app_telemetry_reporter.hxx +59 -0
  18. data/ext/couchbase/core/bucket.cxx +77 -35
  19. data/ext/couchbase/core/bucket.hxx +17 -10
  20. data/ext/couchbase/core/cluster.cxx +54 -16
  21. data/ext/couchbase/core/cluster_credentials.cxx +27 -0
  22. data/ext/couchbase/core/cluster_credentials.hxx +36 -0
  23. data/ext/couchbase/core/cluster_options.hxx +12 -0
  24. data/ext/couchbase/core/collections_component.cxx +7 -5
  25. data/ext/couchbase/core/http_component.cxx +6 -0
  26. data/ext/couchbase/core/impl/binary_collection.cxx +4 -0
  27. data/ext/couchbase/core/impl/bucket_manager.cxx +2 -0
  28. data/ext/couchbase/core/impl/cluster.cxx +9 -0
  29. data/ext/couchbase/core/impl/collection.cxx +2 -0
  30. data/ext/couchbase/core/impl/error.cxx +1 -0
  31. data/ext/couchbase/core/impl/logger.cxx +51 -0
  32. data/ext/couchbase/core/impl/replica_utils.cxx +1 -1
  33. data/ext/couchbase/core/impl/transaction_get_multi_replicas_from_preferred_server_group_spec.cxx +32 -0
  34. data/ext/couchbase/core/impl/transaction_get_multi_spec.cxx +30 -0
  35. data/ext/couchbase/core/impl/transaction_op_error_category.cxx +2 -0
  36. data/ext/couchbase/core/io/config_tracker.cxx +6 -6
  37. data/ext/couchbase/core/io/http_command.hxx +35 -11
  38. data/ext/couchbase/core/io/http_session.cxx +10 -0
  39. data/ext/couchbase/core/io/http_session.hxx +4 -0
  40. data/ext/couchbase/core/io/http_session_manager.hxx +83 -34
  41. data/ext/couchbase/core/io/mcbp_command.hxx +41 -2
  42. data/ext/couchbase/core/io/mcbp_session.cxx +52 -19
  43. data/ext/couchbase/core/io/mcbp_session.hxx +3 -0
  44. data/ext/couchbase/core/logger/logger.cxx +46 -0
  45. data/ext/couchbase/core/logger/logger.hxx +41 -1
  46. data/ext/couchbase/core/management/bucket_settings.hxx +1 -0
  47. data/ext/couchbase/core/management/bucket_settings_json.hxx +4 -0
  48. data/ext/couchbase/core/meta/features.hxx +32 -0
  49. data/ext/couchbase/core/operations/document_analytics.cxx +9 -9
  50. data/ext/couchbase/core/operations/document_append.cxx +1 -0
  51. data/ext/couchbase/core/operations/document_append.hxx +1 -0
  52. data/ext/couchbase/core/operations/document_get_all_replicas.hxx +10 -2
  53. data/ext/couchbase/core/operations/document_lookup_in.cxx +4 -0
  54. data/ext/couchbase/core/operations/document_lookup_in_all_replicas.hxx +14 -2
  55. data/ext/couchbase/core/operations/document_lookup_in_any_replica.hxx +4 -0
  56. data/ext/couchbase/core/operations/document_mutate_in.cxx +4 -0
  57. data/ext/couchbase/core/operations/document_mutate_in.hxx +1 -0
  58. data/ext/couchbase/core/operations/document_prepend.cxx +1 -0
  59. data/ext/couchbase/core/operations/document_prepend.hxx +1 -0
  60. data/ext/couchbase/core/operations/document_query.cxx +12 -10
  61. data/ext/couchbase/core/operations/http_noop.cxx +1 -0
  62. data/ext/couchbase/core/operations/management/bucket_create.cxx +3 -0
  63. data/ext/couchbase/core/operations/management/bucket_update.cxx +3 -0
  64. data/ext/couchbase/core/origin.cxx +0 -5
  65. data/ext/couchbase/core/origin.hxx +2 -11
  66. data/ext/couchbase/core/platform/random.cc +6 -3
  67. data/ext/couchbase/core/platform/random.h +2 -2
  68. data/ext/couchbase/core/protocol/cmd_mutate_in.hxx +9 -0
  69. data/ext/couchbase/core/timeout_defaults.hxx +4 -0
  70. data/ext/couchbase/core/topology/configuration.cxx +10 -13
  71. data/ext/couchbase/core/topology/configuration.hxx +14 -15
  72. data/ext/couchbase/core/topology/configuration_json.hxx +6 -0
  73. data/ext/couchbase/core/transactions/async_attempt_context.hxx +22 -2
  74. data/ext/couchbase/core/transactions/attempt_context.hxx +25 -7
  75. data/ext/couchbase/core/transactions/attempt_context_impl.cxx +688 -238
  76. data/ext/couchbase/core/transactions/attempt_context_impl.hxx +91 -12
  77. data/ext/couchbase/core/transactions/exceptions.cxx +5 -0
  78. data/ext/couchbase/core/transactions/exceptions.hxx +20 -0
  79. data/ext/couchbase/core/transactions/exceptions_fmt.hxx +3 -0
  80. data/ext/couchbase/core/transactions/forward_compat.cxx +71 -6
  81. data/ext/couchbase/core/transactions/forward_compat.hxx +45 -59
  82. data/ext/couchbase/core/transactions/get_multi_orchestrator.cxx +616 -0
  83. data/ext/couchbase/core/transactions/get_multi_orchestrator.hxx +61 -0
  84. data/ext/couchbase/core/transactions/internal/doc_record.cxx +8 -0
  85. data/ext/couchbase/core/transactions/internal/doc_record.hxx +16 -5
  86. data/ext/couchbase/core/transactions/internal/exceptions_internal.hxx +12 -0
  87. data/ext/couchbase/core/transactions/internal/transaction_context.hxx +13 -0
  88. data/ext/couchbase/core/transactions/internal/transaction_fields.hxx +1 -0
  89. data/ext/couchbase/core/transactions/staged_mutation.cxx +277 -96
  90. data/ext/couchbase/core/transactions/staged_mutation.hxx +28 -76
  91. data/ext/couchbase/core/transactions/transaction_context.cxx +33 -0
  92. data/ext/couchbase/core/transactions/transaction_get_multi_mode.hxx +28 -0
  93. data/ext/couchbase/core/transactions/transaction_get_multi_replicas_from_preferred_server_group_mode.hxx +27 -0
  94. data/ext/couchbase/core/transactions/transaction_get_multi_replicas_from_preferred_server_group_result.hxx +71 -0
  95. data/ext/couchbase/core/transactions/transaction_get_multi_result.hxx +66 -0
  96. data/ext/couchbase/core/transactions/transaction_links.hxx +10 -0
  97. data/ext/couchbase/core/transactions/transactions.cxx +8 -3
  98. data/ext/couchbase/core/utils/connection_string.cxx +4 -0
  99. data/ext/couchbase/core/utils/url_codec.cxx +26 -0
  100. data/ext/couchbase/core/utils/url_codec.hxx +11 -0
  101. data/ext/couchbase/core/websocket_codec.cxx +647 -0
  102. data/ext/couchbase/core/websocket_codec.hxx +77 -0
  103. data/ext/couchbase/couchbase/analytics_options.hxx +70 -6
  104. data/ext/couchbase/couchbase/application_telemetry_options.hxx +124 -0
  105. data/ext/couchbase/couchbase/cluster_options.hxx +17 -0
  106. data/ext/couchbase/couchbase/error_codes.hxx +1 -0
  107. data/ext/couchbase/couchbase/logger.hxx +16 -0
  108. data/ext/couchbase/couchbase/management/bucket_settings.hxx +1 -0
  109. data/ext/couchbase/couchbase/query_options.hxx +70 -6
  110. data/ext/couchbase/couchbase/transactions/async_attempt_context.hxx +29 -5
  111. data/ext/couchbase/couchbase/transactions/attempt_context.hxx +24 -7
  112. data/ext/couchbase/couchbase/transactions/transaction_get_multi_mode.hxx +47 -0
  113. data/ext/couchbase/couchbase/transactions/transaction_get_multi_options.hxx +44 -0
  114. data/ext/couchbase/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_mode.hxx +46 -0
  115. data/ext/couchbase/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_options.hxx +48 -0
  116. data/ext/couchbase/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_result.hxx +109 -0
  117. data/ext/couchbase/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_spec.hxx +47 -0
  118. data/ext/couchbase/couchbase/transactions/transaction_get_multi_result.hxx +102 -0
  119. data/ext/couchbase/couchbase/transactions/transaction_get_multi_spec.hxx +45 -0
  120. data/ext/extconf.rb +6 -0
  121. data/ext/rcb_buckets.cxx +26 -0
  122. data/lib/active_support/cache/couchbase_store.rb +1 -1
  123. data/lib/couchbase/cluster.rb +1 -1
  124. data/lib/couchbase/collection.rb +1 -1
  125. data/lib/couchbase/collection_options.rb +2 -2
  126. data/lib/couchbase/management/analytics_index_manager.rb +4 -4
  127. data/lib/couchbase/management/bucket_manager.rb +8 -2
  128. data/lib/couchbase/protostellar/cluster.rb +2 -2
  129. data/lib/couchbase/protostellar/collection.rb +1 -1
  130. data/lib/couchbase/protostellar/management/collection_query_index_manager.rb +1 -1
  131. data/lib/couchbase/protostellar/request_generator/admin/bucket.rb +4 -4
  132. data/lib/couchbase/protostellar/request_generator/admin/collection.rb +6 -6
  133. data/lib/couchbase/protostellar/request_generator/admin/query.rb +13 -13
  134. data/lib/couchbase/protostellar/request_generator/kv.rb +25 -25
  135. data/lib/couchbase/protostellar/request_generator/query.rb +4 -4
  136. data/lib/couchbase/protostellar/request_generator/search.rb +25 -25
  137. data/lib/couchbase/protostellar/response_converter/search.rb +1 -1
  138. data/lib/couchbase/protostellar/retry/reason.rb +1 -1
  139. data/lib/couchbase/protostellar/timeouts.rb +1 -1
  140. data/lib/couchbase/scope.rb +1 -1
  141. data/lib/couchbase/transcoder_flags.rb +1 -1
  142. data/lib/couchbase/utils/stdlib_logger_adapter.rb +1 -1
  143. data/lib/couchbase/version.rb +1 -1
  144. metadata +47 -19
  145. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/COPYING +0 -0
  146. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/cmake/SnappyConfig.cmake.in +0 -0
  147. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/cmake/config.h.in +0 -0
  148. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-c.cc +0 -0
  149. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-c.h +0 -0
  150. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-internal.h +0 -0
  151. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-sinksource.cc +0 -0
  152. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-sinksource.h +0 -0
  153. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-stubs-internal.cc +0 -0
  154. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-stubs-internal.h +0 -0
  155. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-stubs-public.h.in +0 -0
  156. /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy.h +0 -0
@@ -0,0 +1,647 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2024-Present Couchbase, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
6
+ * except in compliance with the License. You may obtain a copy of the License at
7
+ *
8
+ * https://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software distributed under
11
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12
+ * ANY KIND, either express or implied. See the License for the specific language governing
13
+ * permissions and limitations under the License.
14
+ */
15
+
16
+ #include "websocket_codec.hxx"
17
+
18
+ #include "core/crypto/cbcrypto.h"
19
+ #include "core/platform/base64.h"
20
+ #include "core/platform/random.h"
21
+
22
+ #include <llhttp.h>
23
+ #include <spdlog/fmt/bundled/core.h>
24
+
25
+ #include <algorithm>
26
+ #include <array>
27
+ #include <cstdint>
28
+ #include <cstring>
29
+ #include <map>
30
+ #include <optional>
31
+ #include <random>
32
+ #include <variant>
33
+
34
+ namespace couchbase::core
35
+ {
36
+ namespace
37
+ {
38
+ /*
39
+ 0 1 2 3
40
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
41
+ +-+-+-+-+-------+-+-------------+-------------------------------+
42
+ |F|R|R|R| opcode|M| Payload len | Extended payload length |
43
+ |I|S|S|S| (4) |A| (7) | (16/64) |
44
+ |N|V|V|V| |S| | (if payload len==126/127) |
45
+ | |1|2|3| |K| | |
46
+ +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
47
+ | Extended payload length continued, if payload len == 127 |
48
+ + - - - - - - - - - - - - - - - +-------------------------------+
49
+ | | Masking-key, if MASK set to 1 |
50
+ +-------------------------------+-------------------------------+
51
+ | Masking-key (continued) | Payload Data |
52
+ +-------------------------------- - - - - - - - - - - - - - - - +
53
+ : Payload Data continued ... :
54
+ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
55
+ | Payload Data continued ... |
56
+ +---------------------------------------------------------------+
57
+ */
58
+ constexpr std::uint8_t flag_fin{ 0b1000'0000 };
59
+ constexpr std::uint8_t flag_mask{ 0b1000'0000 };
60
+
61
+ constexpr std::uint8_t reserved_bit_mask{ 0b0111'0000 };
62
+ constexpr std::uint8_t opcode_mask{ 0b0000'1111 };
63
+ constexpr std::uint8_t payload_length_7_mask{ 0b0111'1111 };
64
+
65
+ constexpr std::uint8_t opcode_continuation{ 0x00 };
66
+ constexpr std::uint8_t opcode_text{ 0x01 };
67
+ constexpr std::uint8_t opcode_binary{ 0x2 };
68
+ constexpr std::uint8_t opcode_close{ 0x08 };
69
+ constexpr std::uint8_t opcode_ping{ 0x09 };
70
+ constexpr std::uint8_t opcode_pong{ 0x0a };
71
+
72
+ auto
73
+ generate_masking_key() -> std::array<std::byte, 4>
74
+ {
75
+ thread_local std::random_device rd;
76
+ thread_local std::uniform_int_distribution<std::uint16_t> dist(0, 0xff);
77
+
78
+ return {
79
+ static_cast<std::byte>(dist(rd)),
80
+ static_cast<std::byte>(dist(rd)),
81
+ static_cast<std::byte>(dist(rd)),
82
+ static_cast<std::byte>(dist(rd)),
83
+ };
84
+ }
85
+
86
+ auto
87
+ generate_session_key() -> std::string
88
+ {
89
+ const couchbase::core::RandomGenerator randomGenerator;
90
+ std::array<std::byte, 16> key{};
91
+ if (!core::RandomGenerator::getBytes(key.data(), key.size())) {
92
+ throw std::bad_alloc();
93
+ }
94
+ return core::base64::encode(key, false);
95
+ }
96
+
97
+ auto
98
+ signature_is_valid(const std::string& session_key, const std::string& signature) -> bool
99
+ {
100
+ // RFC 6455, Section 1.3
101
+ static const std::string websocket_guid{ "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" };
102
+ const std::string salted_key{ fmt::format("{}{}", session_key, websocket_guid) };
103
+ auto hash = core::crypto::digest(core::crypto::Algorithm::ALG_SHA1, salted_key);
104
+ return core::base64::encode(hash, false) == signature;
105
+ }
106
+
107
+ auto
108
+ case_insensitive_equals(const std::string& str1, const std::string& str2) -> bool
109
+ {
110
+ if (str1.size() != str2.size()) {
111
+ return false;
112
+ }
113
+
114
+ return std::equal(str1.begin(), str1.end(), str2.begin(), [](unsigned char c1, unsigned char c2) {
115
+ return std::tolower(c1) == std::tolower(c2);
116
+ });
117
+ }
118
+
119
+ struct context {
120
+ websocket_callbacks& callbacks;
121
+ const websocket_codec& ws;
122
+ };
123
+ } // namespace
124
+
125
+ class websocket_handler
126
+ {
127
+ public:
128
+ websocket_handler() = default;
129
+ websocket_handler(const websocket_handler&) = delete;
130
+ websocket_handler(websocket_handler&&) noexcept = delete;
131
+ auto operator=(const websocket_handler&) -> websocket_handler& = delete;
132
+ auto operator=(websocket_handler&&) noexcept -> websocket_handler& = delete;
133
+ virtual ~websocket_handler() = default;
134
+
135
+ virtual auto feed(gsl::span<std::byte> data, const context& ctx)
136
+ -> std::unique_ptr<websocket_handler> = 0;
137
+ };
138
+
139
+ namespace
140
+ {
141
+
142
+ void
143
+ mask_payload_data(gsl::span<std::byte> masking_key, gsl::span<std::byte> payload)
144
+ {
145
+ for (std::size_t i = 0; i < payload.size(); ++i) {
146
+ payload[i] ^= masking_key[i % masking_key.size()];
147
+ }
148
+ }
149
+
150
+ struct decoded_frame {
151
+ std::uint8_t type;
152
+ gsl::span<std::byte> payload;
153
+ std::size_t consumed_bytes;
154
+ bool expected_continuation;
155
+ };
156
+
157
+ struct partial_frame {
158
+ std::uint8_t type;
159
+ std::vector<std::byte> payload;
160
+ };
161
+
162
+ struct decoding_error {
163
+ std::string message;
164
+ };
165
+
166
+ struct need_more_data {
167
+ };
168
+
169
+ using decode_status = std::variant<decoded_frame, decoding_error, need_more_data>;
170
+
171
+ auto
172
+ decode_frame(gsl::span<std::byte> data, bool expected_continuation) -> decode_status;
173
+
174
+ class error_handler : public websocket_handler
175
+ {
176
+ private:
177
+ std::string message_;
178
+
179
+ public:
180
+ error_handler(std::string message, const context& ctx)
181
+ : message_{ std::move(message) }
182
+ {
183
+ ctx.callbacks.on_error(ctx.ws, message_);
184
+ }
185
+
186
+ auto feed(gsl::span<std::byte> /* data */, const context& ctx)
187
+ -> std::unique_ptr<websocket_handler> override
188
+ {
189
+ ctx.callbacks.on_error(ctx.ws, message_);
190
+ return nullptr;
191
+ }
192
+ };
193
+
194
+ class data_handler : public websocket_handler
195
+ {
196
+ private:
197
+ std::vector<std::byte> buffer_{};
198
+ std::optional<partial_frame> partial_response_{};
199
+
200
+ public:
201
+ explicit data_handler(const context& ctx, gsl::span<std::byte> remaining = {})
202
+ : buffer_{ remaining.begin(), remaining.end() }
203
+ {
204
+ ctx.callbacks.on_ready(ctx.ws);
205
+ }
206
+
207
+ auto feed(gsl::span<std::byte> data, const context& ctx)
208
+ -> std::unique_ptr<websocket_handler> override
209
+ {
210
+ std::vector<std::byte> buffer{};
211
+ if (!buffer_.empty()) {
212
+ std::swap(buffer_, buffer);
213
+ std::copy(data.begin(), data.end(), std::back_insert_iterator(buffer));
214
+ data = buffer;
215
+ }
216
+ while (!data.empty()) {
217
+ auto status = decode_frame(data, partial_response_.has_value());
218
+ if (std::holds_alternative<decoding_error>(status)) {
219
+ return std::make_unique<error_handler>(
220
+ fmt::format("Decoding error: {}", std::get<decoding_error>(status).message), ctx);
221
+ }
222
+ if (std::holds_alternative<need_more_data>(status)) {
223
+ std::copy(data.begin(), data.end(), std::back_insert_iterator(buffer_));
224
+ return nullptr;
225
+ }
226
+ if (std::holds_alternative<decoded_frame>(status)) {
227
+ auto frame = std::get<decoded_frame>(status);
228
+ switch (frame.type) {
229
+ case opcode_text:
230
+ if (frame.expected_continuation) {
231
+ partial_response_ = partial_frame{
232
+ frame.type,
233
+ { frame.payload.begin(), frame.payload.end() },
234
+ };
235
+ } else {
236
+ ctx.callbacks.on_text(ctx.ws, frame.payload);
237
+ }
238
+ break;
239
+ case opcode_binary:
240
+ if (frame.expected_continuation) {
241
+ partial_response_ = partial_frame{
242
+ frame.type,
243
+ { frame.payload.begin(), frame.payload.end() },
244
+ };
245
+ } else {
246
+ ctx.callbacks.on_binary(ctx.ws, frame.payload);
247
+ }
248
+ break;
249
+ case opcode_close:
250
+ ctx.callbacks.on_close(ctx.ws, frame.payload);
251
+ break;
252
+ case opcode_ping:
253
+ ctx.callbacks.on_ping(ctx.ws, frame.payload);
254
+ break;
255
+ case opcode_pong:
256
+ ctx.callbacks.on_pong(ctx.ws, frame.payload);
257
+ break;
258
+ case opcode_continuation:
259
+ if (auto& response = partial_response_; response.has_value()) {
260
+ if (frame.expected_continuation) {
261
+ std::copy(frame.payload.begin(),
262
+ frame.payload.end(),
263
+ std::back_insert_iterator(response->payload));
264
+ } else {
265
+ if (response->type == opcode_text) {
266
+ ctx.callbacks.on_text(ctx.ws, response->payload);
267
+ } else {
268
+ ctx.callbacks.on_binary(ctx.ws, response->payload);
269
+ }
270
+ }
271
+ } else {
272
+ return std::make_unique<error_handler>("Unexpected continuation frame", ctx);
273
+ }
274
+ break;
275
+ default:
276
+ return std::make_unique<error_handler>(
277
+ fmt::format("Unexpected frame.type: {}", frame.type), ctx);
278
+ }
279
+ data = data.subspan(frame.consumed_bytes);
280
+ }
281
+ }
282
+
283
+ return nullptr;
284
+ }
285
+ };
286
+
287
+ class open_handshake : public websocket_handler
288
+ {
289
+ public:
290
+ open_handshake()
291
+ {
292
+ llhttp_settings_init(&settings_);
293
+ settings_.on_status = on_status;
294
+ settings_.on_header_field = on_header_field;
295
+ settings_.on_header_value = on_header_value;
296
+ settings_.on_body = on_body;
297
+ settings_.on_message_complete = on_message_complete;
298
+ llhttp_init(&parser_, HTTP_RESPONSE, &settings_);
299
+ parser_.data = this;
300
+ }
301
+
302
+ auto feed(gsl::span<std::byte> data, const context& ctx)
303
+ -> std::unique_ptr<websocket_handler> override
304
+ {
305
+ auto error = llhttp_execute(&parser_, reinterpret_cast<const char*>(data.data()), data.size());
306
+ if (error != HPE_OK && error != HPE_PAUSED_UPGRADE) {
307
+ return std::make_unique<error_handler>(
308
+ fmt::format("Failed to parse HTTP response: {}",
309
+ llhttp_errno_name(llhttp_get_errno(&parser_))),
310
+ ctx);
311
+ }
312
+ if (complete_) {
313
+ if (status_code_ != 101) {
314
+ return std::make_unique<error_handler>(
315
+ fmt::format("Response status must be 101. ({} {})", status_code_, status_message_), ctx);
316
+ }
317
+ if (!case_insensitive_equals(headers_["connection"], "upgrade")) {
318
+ return std::make_unique<error_handler>(
319
+ "Request has MUST contain Connection header field with value including \"Upgrade\"", ctx);
320
+ }
321
+ if (!case_insensitive_equals(headers_["upgrade"], "websocket")) {
322
+ return std::make_unique<error_handler>(
323
+ "Request has MUST contain Upgrade header field with value including \"websocket\"", ctx);
324
+ }
325
+
326
+ if (!signature_is_valid(ctx.ws.session_key(), headers_["sec-websocket-accept"])) {
327
+ return std::make_unique<error_handler>(
328
+ "Request has MUST contain Sec-WebSocket-Accept with valid key", ctx);
329
+ }
330
+
331
+ if (error == HPE_PAUSED_UPGRADE) {
332
+ auto bytes_parsed =
333
+ llhttp_get_error_pos(&parser_) - reinterpret_cast<const char*>(data.data());
334
+ return std::make_unique<data_handler>(ctx,
335
+ data.subspan(static_cast<std::size_t>(bytes_parsed)));
336
+ }
337
+ return std::make_unique<data_handler>(ctx);
338
+ }
339
+ return nullptr;
340
+ }
341
+
342
+ private:
343
+ static auto on_status(llhttp_t* parser, const char* at, std::size_t length) -> int
344
+ {
345
+ auto* self = static_cast<open_handshake*>(parser->data);
346
+ self->status_message_.assign(at, length);
347
+ self->status_code_ = parser->status_code;
348
+ return 0;
349
+ }
350
+
351
+ static auto on_header_field(llhttp_t* parser, const char* at, std::size_t length) -> int
352
+ {
353
+ auto* self = static_cast<open_handshake*>(parser->data);
354
+ self->header_field_.clear();
355
+ self->header_field_.reserve(length);
356
+ for (std::size_t i = 0; i < length; ++i) {
357
+ self->header_field_ += static_cast<char>(std::tolower(at[i]));
358
+ }
359
+ return 0;
360
+ }
361
+
362
+ static auto on_header_value(llhttp_t* parser, const char* at, std::size_t length) -> int
363
+ {
364
+ auto* self = static_cast<open_handshake*>(parser->data);
365
+ self->headers_[self->header_field_] = std::string(at, length);
366
+ return 0;
367
+ }
368
+
369
+ static auto on_body(llhttp_t* parser, const char* at, std::size_t length) -> int
370
+ {
371
+ auto* self = static_cast<open_handshake*>(parser->data);
372
+ self->body_.append(std::string_view{ at, length });
373
+ return 0;
374
+ }
375
+
376
+ static auto on_body_after_upgrade(llhttp_t* parser, const char* at, std::size_t length) -> int
377
+ {
378
+ auto* self = static_cast<open_handshake*>(parser->data);
379
+ self->body_ = std::string_view{ at, length };
380
+ return 0;
381
+ }
382
+
383
+ static auto on_message_complete(llhttp_t* parser) -> int
384
+ {
385
+ auto* self = static_cast<open_handshake*>(parser->data);
386
+ self->complete_ = true;
387
+ return 0;
388
+ }
389
+
390
+ llhttp_settings_t settings_{};
391
+ llhttp_t parser_{};
392
+
393
+ std::string header_field_{};
394
+ bool complete_{};
395
+
396
+ std::uint32_t status_code_{ 0 };
397
+ std::string status_message_{};
398
+ std::map<std::string, std::string> headers_{};
399
+ std::string body_{};
400
+ };
401
+
402
+ constexpr auto
403
+ is_data_frame(std::uint8_t opcode) -> bool
404
+ {
405
+ switch (opcode) {
406
+ case opcode_text:
407
+ case opcode_binary:
408
+ return true;
409
+ default:
410
+ break;
411
+ }
412
+ return false;
413
+ }
414
+
415
+ constexpr auto
416
+ is_control_frame(std::uint8_t opcode) -> bool
417
+ {
418
+ switch (opcode) {
419
+ case opcode_close:
420
+ case opcode_ping:
421
+ case opcode_pong:
422
+ return true;
423
+ default:
424
+ break;
425
+ }
426
+ return false;
427
+ }
428
+
429
+ auto
430
+ encode_payload_length(std::size_t length) -> std::vector<std::byte>
431
+ {
432
+ if (length <= 125) { // 7 bit
433
+ const std::uint8_t field_7_bit = static_cast<std::uint8_t>(length) | flag_mask;
434
+ return { std::byte{ field_7_bit } };
435
+ }
436
+ if (length <= 0xFFFF) { // 7 + 16 bit
437
+ constexpr std::uint8_t field_7_bit = 126 | flag_mask;
438
+ return {
439
+ std::byte{ field_7_bit },
440
+ static_cast<std::byte>((length >> 8) & 0xff),
441
+ static_cast<std::byte>(length & 0xff),
442
+ };
443
+ }
444
+ // 7 + 64
445
+ constexpr std::uint8_t field_7_bit = 127 | flag_mask;
446
+ return {
447
+ std::byte{ field_7_bit },
448
+ static_cast<std::byte>((length >> 56) & 0xff),
449
+ static_cast<std::byte>((length >> 48) & 0xff),
450
+ static_cast<std::byte>((length >> 40) & 0xff),
451
+ static_cast<std::byte>((length >> 32) & 0xff),
452
+ static_cast<std::byte>((length >> 24) & 0xff),
453
+ static_cast<std::byte>((length >> 16) & 0xff),
454
+ static_cast<std::byte>((length >> 8) & 0xff),
455
+ static_cast<std::byte>(length & 0xff),
456
+ };
457
+ }
458
+
459
+ auto
460
+ decode_uint64(gsl::span<std::byte> data) -> std::uint64_t
461
+ {
462
+ std::uint64_t result{};
463
+ std::memcpy(&result, data.data(), sizeof(result));
464
+
465
+ return //
466
+ (result & 0x00000000000000FF) << 56 | //
467
+ (result & 0x000000000000FF00) << 40 | //
468
+ (result & 0x0000000000FF0000) << 24 | //
469
+ (result & 0x00000000FF000000) << 8 | //
470
+ (result & 0x000000FF00000000) >> 8 | //
471
+ (result & 0x0000FF0000000000) >> 24 | //
472
+ (result & 0x00FF000000000000) >> 40 | //
473
+ (result & 0xFF00000000000000) >> 56;
474
+ }
475
+
476
+ auto
477
+ decode_uint16(gsl::span<std::byte> data) -> std::uint16_t
478
+ {
479
+ std::uint16_t result{};
480
+ std::memcpy(&result, data.data(), sizeof(result));
481
+
482
+ return static_cast<std::uint16_t>(result << 8 | result >> 8);
483
+ }
484
+
485
+ auto
486
+ decode_frame(gsl::span<std::byte> data, bool expected_continuation) -> decode_status
487
+ {
488
+ auto first_byte = static_cast<std::uint8_t>(data[0]);
489
+ if ((first_byte & reserved_bit_mask) != 0) {
490
+ return decoding_error{ "unsupported error: reserved bit used" };
491
+ }
492
+
493
+ const bool expect_more = (first_byte & flag_fin) == 0;
494
+ const std::uint8_t frame_type = first_byte & opcode_mask;
495
+
496
+ if (expect_more && is_control_frame(frame_type)) {
497
+ return decoding_error{ "unsupported error: fragmented control frame" };
498
+ }
499
+
500
+ if (is_data_frame(frame_type) && expected_continuation) {
501
+ return decoding_error{ "unsupported error: expected continuation frame" };
502
+ }
503
+
504
+ const auto second_byte = static_cast<std::uint8_t>(data[1]);
505
+
506
+ const bool masked = (second_byte & flag_mask) != 0;
507
+
508
+ const std::uint8_t length_7 = second_byte & payload_length_7_mask;
509
+ if (is_control_frame(frame_type) && length_7 > 125) {
510
+ return decoding_error{ "unsupported error: control frame is too long" };
511
+ }
512
+
513
+ std::size_t header_length{};
514
+ std::size_t payload_length{};
515
+
516
+ switch (length_7) {
517
+ case 127:
518
+ if (data.size() < 10) {
519
+ return need_more_data{};
520
+ }
521
+ header_length = 10;
522
+ payload_length = decode_uint64(data.subspan(2, sizeof(std::uint64_t)));
523
+ break;
524
+
525
+ case 126:
526
+ if (data.size() < 4) {
527
+ return need_more_data{};
528
+ }
529
+ header_length = 4;
530
+ payload_length = decode_uint16(data.subspan(2, sizeof(std::uint16_t)));
531
+ break;
532
+
533
+ default:
534
+ header_length = 2;
535
+ payload_length = length_7;
536
+ }
537
+
538
+ constexpr std::size_t masking_key_size{ 4 };
539
+ if (data.size() < header_length + payload_length + (masked ? masking_key_size : 0)) {
540
+ return need_more_data{};
541
+ }
542
+ auto consumed_bytes = header_length;
543
+
544
+ auto payload = data.subspan(header_length);
545
+ if (masked) {
546
+ auto masking_key = data.subspan(header_length, masking_key_size);
547
+ consumed_bytes += masking_key.size();
548
+ payload = data.subspan(header_length + masking_key.size());
549
+ mask_payload_data(masking_key, payload);
550
+ }
551
+ consumed_bytes += payload.size();
552
+
553
+ return decoded_frame{
554
+ frame_type,
555
+ payload,
556
+ consumed_bytes,
557
+ expect_more,
558
+ };
559
+ }
560
+
561
+ template<typename T>
562
+ auto
563
+ encode_frame(std::uint8_t opcode, T message) -> std::vector<std::byte>
564
+ {
565
+ std::vector<std::byte> payload{};
566
+ payload.reserve(7 + message.size());
567
+ payload.emplace_back(static_cast<std::byte>(opcode | flag_fin));
568
+ auto encoded_length = encode_payload_length(message.size());
569
+ std::copy(encoded_length.begin(), encoded_length.end(), std::back_insert_iterator(payload));
570
+ auto masking_key = generate_masking_key();
571
+ std::copy(masking_key.begin(), masking_key.end(), std::back_insert_iterator(payload));
572
+ auto header_length = payload.size();
573
+ std::for_each(message.begin(), message.end(), [&payload](auto ch) {
574
+ payload.push_back(static_cast<std::byte>(ch));
575
+ });
576
+ mask_payload_data(masking_key,
577
+ { payload.data() + header_length, payload.size() - header_length });
578
+ return payload;
579
+ }
580
+ } // namespace
581
+
582
+ websocket_codec::websocket_codec(websocket_callbacks* callbacks)
583
+ : session_key_{ generate_session_key() }
584
+ , callbacks_{ callbacks }
585
+ , handler_{ std::make_unique<open_handshake>() }
586
+ {
587
+ }
588
+
589
+ auto
590
+ websocket_codec::session_key() const -> const std::string&
591
+ {
592
+ return session_key_;
593
+ }
594
+
595
+ websocket_codec::~websocket_codec() = default;
596
+
597
+ void
598
+ websocket_codec::feed(gsl::span<std::byte> chunk)
599
+ {
600
+ auto new_handler = handler_->feed(chunk,
601
+ context{
602
+ *callbacks_,
603
+ *this,
604
+ });
605
+ if (new_handler) {
606
+ std::swap(handler_, new_handler);
607
+ }
608
+ }
609
+
610
+ void
611
+ websocket_codec::feed(std::string_view chunk)
612
+ {
613
+ std::vector<std::byte> copy{ reinterpret_cast<const std::byte*>(chunk.data()),
614
+ reinterpret_cast<const std::byte*>(chunk.data()) + chunk.size() };
615
+ feed(copy);
616
+ }
617
+
618
+ auto
619
+ websocket_codec::text(std::string_view message) const -> std::vector<std::byte>
620
+ {
621
+ return encode_frame(opcode_text, message);
622
+ }
623
+
624
+ auto
625
+ websocket_codec::binary(gsl::span<std::byte> message) const -> std::vector<std::byte>
626
+ {
627
+ return encode_frame(opcode_binary, message);
628
+ }
629
+
630
+ auto
631
+ websocket_codec::ping(gsl::span<std::byte> message) const -> std::vector<std::byte>
632
+ {
633
+ return encode_frame(opcode_ping, message);
634
+ }
635
+
636
+ auto
637
+ websocket_codec::pong(gsl::span<std::byte> message) const -> std::vector<std::byte>
638
+ {
639
+ return encode_frame(opcode_pong, message);
640
+ }
641
+
642
+ auto
643
+ websocket_codec::close(gsl::span<std::byte> message) const -> std::vector<std::byte>
644
+ {
645
+ return encode_frame(opcode_close, message);
646
+ }
647
+ } // namespace couchbase::core