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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/ext/cache/extconf_include.rb +3 -3
- data/ext/cache/mozilla-ca-bundle.crt +3 -165
- data/ext/cache/mozilla-ca-bundle.sha256 +1 -1
- data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/CMakeLists.txt +14 -10
- data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy.cc +7 -4
- data/ext/couchbase/CMakeLists.txt +12 -1
- data/ext/couchbase/cmake/Profiler.cmake +15 -0
- data/ext/couchbase/cmake/ThirdPartyDependencies.cmake +2 -2
- data/ext/couchbase/cmake/couchbase_cxx_client.pc.in +1 -1
- data/ext/couchbase/core/app_telemetry_address.cxx +55 -0
- data/ext/couchbase/core/app_telemetry_address.hxx +39 -0
- data/ext/couchbase/core/app_telemetry_meter.cxx +753 -0
- data/ext/couchbase/core/app_telemetry_meter.hxx +198 -0
- data/ext/couchbase/core/app_telemetry_reporter.cxx +895 -0
- data/ext/couchbase/core/app_telemetry_reporter.hxx +59 -0
- data/ext/couchbase/core/bucket.cxx +77 -35
- data/ext/couchbase/core/bucket.hxx +17 -10
- data/ext/couchbase/core/cluster.cxx +54 -16
- data/ext/couchbase/core/cluster_credentials.cxx +27 -0
- data/ext/couchbase/core/cluster_credentials.hxx +36 -0
- data/ext/couchbase/core/cluster_options.hxx +12 -0
- data/ext/couchbase/core/collections_component.cxx +7 -5
- data/ext/couchbase/core/http_component.cxx +6 -0
- data/ext/couchbase/core/impl/binary_collection.cxx +4 -0
- data/ext/couchbase/core/impl/bucket_manager.cxx +2 -0
- data/ext/couchbase/core/impl/cluster.cxx +9 -0
- data/ext/couchbase/core/impl/collection.cxx +2 -0
- data/ext/couchbase/core/impl/error.cxx +1 -0
- data/ext/couchbase/core/impl/logger.cxx +51 -0
- data/ext/couchbase/core/impl/replica_utils.cxx +1 -1
- data/ext/couchbase/core/impl/transaction_get_multi_replicas_from_preferred_server_group_spec.cxx +32 -0
- data/ext/couchbase/core/impl/transaction_get_multi_spec.cxx +30 -0
- data/ext/couchbase/core/impl/transaction_op_error_category.cxx +2 -0
- data/ext/couchbase/core/io/config_tracker.cxx +6 -6
- data/ext/couchbase/core/io/http_command.hxx +35 -11
- data/ext/couchbase/core/io/http_session.cxx +10 -0
- data/ext/couchbase/core/io/http_session.hxx +4 -0
- data/ext/couchbase/core/io/http_session_manager.hxx +83 -34
- data/ext/couchbase/core/io/mcbp_command.hxx +41 -2
- data/ext/couchbase/core/io/mcbp_session.cxx +52 -19
- data/ext/couchbase/core/io/mcbp_session.hxx +3 -0
- data/ext/couchbase/core/logger/logger.cxx +46 -0
- data/ext/couchbase/core/logger/logger.hxx +41 -1
- data/ext/couchbase/core/management/bucket_settings.hxx +1 -0
- data/ext/couchbase/core/management/bucket_settings_json.hxx +4 -0
- data/ext/couchbase/core/meta/features.hxx +32 -0
- data/ext/couchbase/core/operations/document_analytics.cxx +9 -9
- data/ext/couchbase/core/operations/document_append.cxx +1 -0
- data/ext/couchbase/core/operations/document_append.hxx +1 -0
- data/ext/couchbase/core/operations/document_get_all_replicas.hxx +10 -2
- data/ext/couchbase/core/operations/document_lookup_in.cxx +4 -0
- data/ext/couchbase/core/operations/document_lookup_in_all_replicas.hxx +14 -2
- data/ext/couchbase/core/operations/document_lookup_in_any_replica.hxx +4 -0
- data/ext/couchbase/core/operations/document_mutate_in.cxx +4 -0
- data/ext/couchbase/core/operations/document_mutate_in.hxx +1 -0
- data/ext/couchbase/core/operations/document_prepend.cxx +1 -0
- data/ext/couchbase/core/operations/document_prepend.hxx +1 -0
- data/ext/couchbase/core/operations/document_query.cxx +12 -10
- data/ext/couchbase/core/operations/http_noop.cxx +1 -0
- data/ext/couchbase/core/operations/management/bucket_create.cxx +3 -0
- data/ext/couchbase/core/operations/management/bucket_update.cxx +3 -0
- data/ext/couchbase/core/origin.cxx +0 -5
- data/ext/couchbase/core/origin.hxx +2 -11
- data/ext/couchbase/core/platform/random.cc +6 -3
- data/ext/couchbase/core/platform/random.h +2 -2
- data/ext/couchbase/core/protocol/cmd_mutate_in.hxx +9 -0
- data/ext/couchbase/core/timeout_defaults.hxx +4 -0
- data/ext/couchbase/core/topology/configuration.cxx +10 -13
- data/ext/couchbase/core/topology/configuration.hxx +14 -15
- data/ext/couchbase/core/topology/configuration_json.hxx +6 -0
- data/ext/couchbase/core/transactions/async_attempt_context.hxx +22 -2
- data/ext/couchbase/core/transactions/attempt_context.hxx +25 -7
- data/ext/couchbase/core/transactions/attempt_context_impl.cxx +688 -238
- data/ext/couchbase/core/transactions/attempt_context_impl.hxx +91 -12
- data/ext/couchbase/core/transactions/exceptions.cxx +5 -0
- data/ext/couchbase/core/transactions/exceptions.hxx +20 -0
- data/ext/couchbase/core/transactions/exceptions_fmt.hxx +3 -0
- data/ext/couchbase/core/transactions/forward_compat.cxx +71 -6
- data/ext/couchbase/core/transactions/forward_compat.hxx +45 -59
- data/ext/couchbase/core/transactions/get_multi_orchestrator.cxx +616 -0
- data/ext/couchbase/core/transactions/get_multi_orchestrator.hxx +61 -0
- data/ext/couchbase/core/transactions/internal/doc_record.cxx +8 -0
- data/ext/couchbase/core/transactions/internal/doc_record.hxx +16 -5
- data/ext/couchbase/core/transactions/internal/exceptions_internal.hxx +12 -0
- data/ext/couchbase/core/transactions/internal/transaction_context.hxx +13 -0
- data/ext/couchbase/core/transactions/internal/transaction_fields.hxx +1 -0
- data/ext/couchbase/core/transactions/staged_mutation.cxx +277 -96
- data/ext/couchbase/core/transactions/staged_mutation.hxx +28 -76
- data/ext/couchbase/core/transactions/transaction_context.cxx +33 -0
- data/ext/couchbase/core/transactions/transaction_get_multi_mode.hxx +28 -0
- data/ext/couchbase/core/transactions/transaction_get_multi_replicas_from_preferred_server_group_mode.hxx +27 -0
- data/ext/couchbase/core/transactions/transaction_get_multi_replicas_from_preferred_server_group_result.hxx +71 -0
- data/ext/couchbase/core/transactions/transaction_get_multi_result.hxx +66 -0
- data/ext/couchbase/core/transactions/transaction_links.hxx +10 -0
- data/ext/couchbase/core/transactions/transactions.cxx +8 -3
- data/ext/couchbase/core/utils/connection_string.cxx +4 -0
- data/ext/couchbase/core/utils/url_codec.cxx +26 -0
- data/ext/couchbase/core/utils/url_codec.hxx +11 -0
- data/ext/couchbase/core/websocket_codec.cxx +647 -0
- data/ext/couchbase/core/websocket_codec.hxx +77 -0
- data/ext/couchbase/couchbase/analytics_options.hxx +70 -6
- data/ext/couchbase/couchbase/application_telemetry_options.hxx +124 -0
- data/ext/couchbase/couchbase/cluster_options.hxx +17 -0
- data/ext/couchbase/couchbase/error_codes.hxx +1 -0
- data/ext/couchbase/couchbase/logger.hxx +16 -0
- data/ext/couchbase/couchbase/management/bucket_settings.hxx +1 -0
- data/ext/couchbase/couchbase/query_options.hxx +70 -6
- data/ext/couchbase/couchbase/transactions/async_attempt_context.hxx +29 -5
- data/ext/couchbase/couchbase/transactions/attempt_context.hxx +24 -7
- data/ext/couchbase/couchbase/transactions/transaction_get_multi_mode.hxx +47 -0
- data/ext/couchbase/couchbase/transactions/transaction_get_multi_options.hxx +44 -0
- data/ext/couchbase/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_mode.hxx +46 -0
- data/ext/couchbase/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_options.hxx +48 -0
- data/ext/couchbase/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_result.hxx +109 -0
- data/ext/couchbase/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_spec.hxx +47 -0
- data/ext/couchbase/couchbase/transactions/transaction_get_multi_result.hxx +102 -0
- data/ext/couchbase/couchbase/transactions/transaction_get_multi_spec.hxx +45 -0
- data/ext/extconf.rb +6 -0
- data/ext/rcb_buckets.cxx +26 -0
- data/lib/active_support/cache/couchbase_store.rb +1 -1
- data/lib/couchbase/cluster.rb +1 -1
- data/lib/couchbase/collection.rb +1 -1
- data/lib/couchbase/collection_options.rb +2 -2
- data/lib/couchbase/management/analytics_index_manager.rb +4 -4
- data/lib/couchbase/management/bucket_manager.rb +8 -2
- data/lib/couchbase/protostellar/cluster.rb +2 -2
- data/lib/couchbase/protostellar/collection.rb +1 -1
- data/lib/couchbase/protostellar/management/collection_query_index_manager.rb +1 -1
- data/lib/couchbase/protostellar/request_generator/admin/bucket.rb +4 -4
- data/lib/couchbase/protostellar/request_generator/admin/collection.rb +6 -6
- data/lib/couchbase/protostellar/request_generator/admin/query.rb +13 -13
- data/lib/couchbase/protostellar/request_generator/kv.rb +25 -25
- data/lib/couchbase/protostellar/request_generator/query.rb +4 -4
- data/lib/couchbase/protostellar/request_generator/search.rb +25 -25
- data/lib/couchbase/protostellar/response_converter/search.rb +1 -1
- data/lib/couchbase/protostellar/retry/reason.rb +1 -1
- data/lib/couchbase/protostellar/timeouts.rb +1 -1
- data/lib/couchbase/scope.rb +1 -1
- data/lib/couchbase/transcoder_flags.rb +1 -1
- data/lib/couchbase/utils/stdlib_logger_adapter.rb +1 -1
- data/lib/couchbase/version.rb +1 -1
- metadata +47 -19
- /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/COPYING +0 -0
- /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/cmake/SnappyConfig.cmake.in +0 -0
- /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/cmake/config.h.in +0 -0
- /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-c.cc +0 -0
- /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-c.h +0 -0
- /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-internal.h +0 -0
- /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-sinksource.cc +0 -0
- /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-sinksource.h +0 -0
- /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-stubs-internal.cc +0 -0
- /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-stubs-internal.h +0 -0
- /data/ext/cache/snappy/{585305c8dbb8f762f2c2e17f937f1cf3ac6cbc9c → 3cde171792b3607f75c14e5011eaf69da4857bd8}/snappy/snappy-stubs-public.h.in +0 -0
- /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
|