1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3 * Copyright 2017 Couchbase, Inc.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 #include "executors.h"
19
20 #include <daemon/cookie.h>
21
22 #include <set>
23
24 // We can't use a set of enums that easily in an unordered_set.. just use an
25 // ordered for now..
26 using FeatureSet = std::set<cb::mcbp::Feature>;
27
28 /**
29 * Try to see if the provided vector of features contais a certain feature
30 *
31 * @param features The vector to search
32 * @param feature The feature to check for
33 * @return true if it contains the feature, false otherwise
34 */
containsFeature(const FeatureSet& features, cb::mcbp::Feature feature)35 bool containsFeature(const FeatureSet& features, cb::mcbp::Feature feature) {
36 return features.find(feature) != features.end();
37 }
38
39 /**
40 * Convert the input array of requested features into the Feature set which
41 * don't include any illegal / unsupported features or any duplicates.
42 *
43 * In addition to that we'll also make sure that all dependent features is
44 * enabled (and that we don't request features which are mutually exclusive)
45 *
46 * @param requested The set to populate with the requested features
47 * @param input The input array
48 */
buildRequestVector(FeatureSet& requested, cb::sized_buffer<const uint16_t> input)49 void buildRequestVector(FeatureSet& requested, cb::sized_buffer<const uint16_t> input) {
50 for (const auto& value : input) {
51 const uint16_t in = ntohs(value);
52 const auto feature = cb::mcbp::Feature(in);
53
54 switch (feature) {
55 case cb::mcbp::Feature::Invalid:
56 case cb::mcbp::Feature::TLS:
57 // known, but we don't support them
58 break;
59 case cb::mcbp::Feature::TCPNODELAY:
60 case cb::mcbp::Feature::TCPDELAY:
61 case cb::mcbp::Feature::MUTATION_SEQNO:
62 case cb::mcbp::Feature::XATTR:
63 case cb::mcbp::Feature::JSON:
64 case cb::mcbp::Feature::SNAPPY:
65 case cb::mcbp::Feature::XERROR:
66 case cb::mcbp::Feature::SELECT_BUCKET:
67 case cb::mcbp::Feature::COLLECTIONS:
68 case cb::mcbp::Feature::Duplex:
69 case cb::mcbp::Feature::ClustermapChangeNotification:
70 case cb::mcbp::Feature::UnorderedExecution:
71 case cb::mcbp::Feature::Tracing:
72
73 // This isn't very optimal, but we've only got a handfull of elements ;)
74 if (!containsFeature(requested, feature)) {
75 requested.insert(feature);
76 }
77
78 break;
79 }
80 }
81
82 // Run through the requested array and make sure we don't have
83 // illegal combinations
84 for (const auto& feature : requested) {
85 switch (cb::mcbp::Feature(feature)) {
86 case cb::mcbp::Feature::Invalid:
87 case cb::mcbp::Feature::TLS:
88 case cb::mcbp::Feature::MUTATION_SEQNO:
89 case cb::mcbp::Feature::XATTR:
90 case cb::mcbp::Feature::XERROR:
91 case cb::mcbp::Feature::SELECT_BUCKET:
92 case cb::mcbp::Feature::COLLECTIONS:
93 case cb::mcbp::Feature::SNAPPY:
94 case cb::mcbp::Feature::JSON:
95 case cb::mcbp::Feature::Tracing:
96 case cb::mcbp::Feature::Duplex:
97 case cb::mcbp::Feature::UnorderedExecution:
98 // No other dependency
99 break;
100
101 case cb::mcbp::Feature::TCPNODELAY:
102 // cannot co-exist with TCPDELAY
103 if (containsFeature(requested, cb::mcbp::Feature::TCPDELAY)) {
104 throw std::invalid_argument("TCPNODELAY cannot co-exist with TCPDELAY");
105 }
106 break;
107 case cb::mcbp::Feature::TCPDELAY:
108 // cannot co-exist with TCPNODELAY
109 if (containsFeature(requested, cb::mcbp::Feature::TCPNODELAY)) {
110 throw std::invalid_argument("TCPDELAY cannot co-exist with TCPNODELAY");
111 }
112 break;
113 case cb::mcbp::Feature::ClustermapChangeNotification:
114 // Needs duplex
115 if (!containsFeature(requested, cb::mcbp::Feature::Duplex)) {
116 throw std::invalid_argument("ClustermapChangeNotification needs Duplex");
117 }
118 break;
119 }
120 }
121 }
122
process_hello_packet_executor(Cookie& cookie)123 void process_hello_packet_executor(Cookie& cookie) {
124 auto& connection = cookie.getConnection();
125 auto* req = reinterpret_cast<protocol_binary_request_hello*>(
126 cookie.getPacketAsVoidPtr());
127 std::string log_buffer;
128 log_buffer.reserve(512);
129 log_buffer.append("HELO ");
130
131 const cb::const_char_buffer key{
132 reinterpret_cast<const char*>(req->bytes + sizeof(req->bytes)),
133 ntohs(req->message.header.request.keylen)};
134
135 const cb::sized_buffer<const uint16_t> input{
136 reinterpret_cast<const uint16_t*>(key.data() + key.size()),
137 (ntohl(req->message.header.request.bodylen) - key.size()) / 2};
138
139 std::vector<uint16_t> out;
140
141 // We can't switch bucket if we've got multiple commands in flight
142 if (connection.getNumberOfCookies() > 1) {
143 LOG_INFO(
144 "{}: {} Changing options via HELO is not possible with "
145 "multiple "
146 "commands in flight",
147 connection.getId(),
148 connection.getDescription());
149 cookie.sendResponse(cb::mcbp::Status::NotSupported);
150 return;
151 }
152
153 FeatureSet requested;
154 try {
155 buildRequestVector(requested, input);
156 } catch (const std::invalid_argument& e) {
157 LOG_INFO("{}: {} Invalid combination of options: {}",
158 connection.getId(),
159 connection.getDescription(),
160 e.what());
161 cookie.setErrorContext(e.what());
162 cookie.sendResponse(cb::mcbp::Status::Einval);
163 return;
164 }
165
166 /*
167 * Disable all features the hello packet may enable, so that
168 * the client can toggle features on/off during a connection
169 */
170 connection.disableAllDatatypes();
171 connection.setSupportsMutationExtras(false);
172 connection.setXerrorSupport(false);
173 connection.setCollectionsSupported(false);
174 connection.setDuplexSupported(false);
175 connection.setClustermapChangeNotificationSupported(false);
176 connection.setTracingEnabled(false);
177 connection.setAllowUnorderedExecution(false);
178
179 if (!key.empty()) {
180 if (key.front() == '{') {
181 // This may be JSON
182 const auto data = to_string(key);
183 unique_cJSON_ptr json{cJSON_Parse(data.c_str())};
184 if (json) {
185 auto* obj = cJSON_GetObjectItem(json.get(), "i");
186 if (obj != nullptr && obj->type == cJSON_String) {
187 try {
188 connection.setConnectionId(obj->valuestring);
189 } catch (const std::exception& exception) {
190 LOG_INFO("{}: Failed to parse connection uuid: {}",
191 connection.getId(),
192 exception.what());
193 }
194 }
195 obj = cJSON_GetObjectItem(json.get(), "a");
196 if (obj != nullptr && obj->type == cJSON_String) {
197 connection.setAgentName(obj->valuestring);
198 }
199 } else {
200 connection.setAgentName(key);
201 }
202 } else {
203 connection.setAgentName(key);
204 }
205
206 log_buffer.append("[");
207 log_buffer.append(key.data(), key.size());
208 log_buffer.append("] ");
209 }
210
211 for (const auto& feature : requested) {
212 bool added = false;
213
214 switch (feature) {
215 case cb::mcbp::Feature::Invalid:
216 case cb::mcbp::Feature::TLS:
217 // Not implemented
218 LOG_INFO("{}: {} requested unupported feature {}",
219 connection.getId(),
220 connection.getDescription(),
221 to_string(feature));
222 break;
223 case cb::mcbp::Feature::TCPNODELAY:
224 case cb::mcbp::Feature::TCPDELAY:
225 connection.setTcpNoDelay(feature == cb::mcbp::Feature::TCPNODELAY);
226 added = true;
227 break;
228
229 case cb::mcbp::Feature::MUTATION_SEQNO:
230 connection.setSupportsMutationExtras(true);
231 added = true;
232 break;
233 case cb::mcbp::Feature::XATTR:
234 if ((Datatype::isSupported(cb::mcbp::Feature::XATTR) ||
235 connection.isInternal())) {
236 connection.enableDatatype(cb::mcbp::Feature::XATTR);
237 added = true;
238 }
239 break;
240 case cb::mcbp::Feature::JSON:
241 if (Datatype::isSupported(cb::mcbp::Feature::JSON)) {
242 connection.enableDatatype(cb::mcbp::Feature::JSON);
243 added = true;
244 }
245 break;
246 case cb::mcbp::Feature::SNAPPY:
247 if (Datatype::isSupported(cb::mcbp::Feature::SNAPPY)) {
248 connection.enableDatatype(cb::mcbp::Feature::SNAPPY);
249 added = true;
250 }
251 break;
252 case cb::mcbp::Feature::XERROR:
253 connection.setXerrorSupport(true);
254 added = true;
255 break;
256 case cb::mcbp::Feature::SELECT_BUCKET:
257 // The select bucket is only informative ;-)
258 added = true;
259 break;
260 case cb::mcbp::Feature::COLLECTIONS:
261 connection.setCollectionsSupported(true);
262 added = true;
263 break;
264 case cb::mcbp::Feature::Duplex:
265 connection.setDuplexSupported(true);
266 added = true;
267 break;
268 case cb::mcbp::Feature::ClustermapChangeNotification:
269 connection.setClustermapChangeNotificationSupported(true);
270 added = true;
271 break;
272 case cb::mcbp::Feature::UnorderedExecution:
273 if (connection.isDCP()) {
274 LOG_INFO(
275 "{}: {} Unordered execution is not supported for "
276 "DCP connections",
277 connection.getId(),
278 connection.getDescription());
279 } else {
280 connection.setAllowUnorderedExecution(true);
281 added = true;
282 }
283 break;
284
285 case cb::mcbp::Feature::Tracing:
286 if (settings.isTracingEnabled()) {
287 connection.setTracingEnabled(true);
288 added = true;
289 break;
290 } else {
291 LOG_INFO("{}: {} Request for [disabled] Tracing feature",
292 connection.getId(),
293 connection.getDescription());
294 }
295
296 } // end switch
297
298 if (added) {
299 out.push_back(htons(uint16_t(feature)));
300 log_buffer.append(to_string(feature));
301 log_buffer.append(", ");
302 }
303 }
304
305 cookie.sendResponse(
306 cb::mcbp::Status::Success,
307 {},
308 {},
309 {reinterpret_cast<const char*>(out.data()), 2 * out.size()},
310 cb::mcbp::Datatype::Raw,
311 0);
312
313 // Trim off the trailing whitespace (and potentially comma)
314 log_buffer.resize(log_buffer.size() - 1);
315 if (log_buffer.back() == ',') {
316 log_buffer.resize(log_buffer.size() - 1);
317 }
318
319 LOG_INFO("{}: {} {}",
320 connection.getId(),
321 log_buffer,
322 connection.getDescription());
323 }
324