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