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 /*
19  *  Code common to various sub-document test cases.
20  */
21 
22 #include "testapp_subdoc_common.h"
23 
operator <<(std::ostream& os, const BinprotSubdocCommand& obj)24 std::ostream& operator<<(std::ostream& os, const BinprotSubdocCommand& obj) {
25     os << "[cmd:" << to_string(cb::mcbp::ClientOpcode(obj.getOp()))
26        << " key:" << obj.getKey() << " path:" << obj.getPath()
27        << " value:" << obj.getValue() << " flags:" << obj.getFlags()
28        << " cas:" << obj.getCas() << "]";
29     return os;
30 }
31 
32 /* Encodes and sends a sub-document command, without waiting for any response.
33  */
send_subdoc_cmd(const BinprotSubdocCommand& cmd)34 void send_subdoc_cmd(const BinprotSubdocCommand& cmd) {
35     std::vector<uint8_t> buf;
36     cmd.encode(buf);
37     safe_send(buf.data(), buf.size(), false);
38 }
39 
recv_subdoc_response(const BinprotSubdocCommand& cmd, cb::mcbp::Status err, BinprotSubdocResponse& resp)40 static void recv_subdoc_response(const BinprotSubdocCommand& cmd,
41                                  cb::mcbp::Status err,
42                                  BinprotSubdocResponse& resp) {
43     std::vector<uint8_t> buf;
44     if (!safe_recv_packet(buf)) {
45         ADD_FAILURE() << "Failed to recv subdoc response";
46         return;
47     }
48 
49     protocol_binary_response_no_extras tempResponse;
50     memcpy(&tempResponse, buf.data(), sizeof tempResponse);
51     mcbp_validate_response_header(&tempResponse, cmd.getOp(), err);
52     // safe_recv_packet does ntohl already!
53     resp.assign(std::move(buf));
54 }
55 
recv_subdoc_response(cb::mcbp::ClientOpcode expected_cmd, cb::mcbp::Status expected_status, const std::string& expected_value)56 uint64_t recv_subdoc_response(cb::mcbp::ClientOpcode expected_cmd,
57                               cb::mcbp::Status expected_status,
58                               const std::string& expected_value) {
59     union {
60         protocol_binary_response_subdocument response;
61         char bytes[1024];
62     } receive;
63 
64     if (!safe_recv_packet(receive.bytes, sizeof(receive.bytes))) {
65         ADD_FAILURE() << "Failed to recv subdoc response";
66         return -1;
67     }
68 
69     mcbp_validate_response_header(
70             (protocol_binary_response_no_extras*)&receive.response,
71             expected_cmd,
72             expected_status);
73 
74     const protocol_binary_response_header* header =
75             &receive.response.message.header;
76 
77     const char* val_ptr =
78             receive.bytes + sizeof(*header) + header->response.getExtlen();
79     const size_t vallen =
80             header->response.getBodylen() - header->response.getExtlen();
81 
82     if (!expected_value.empty() &&
83         (expected_cmd != cb::mcbp::ClientOpcode::SubdocExists)) {
84         const std::string val(val_ptr, val_ptr + vallen);
85         EXPECT_EQ(expected_value, val);
86     } else {
87         // Expect zero length on success (on error the error message string is
88         // returned).
89         if (header->response.getStatus() == cb::mcbp::Status::Success) {
90             EXPECT_EQ(0u, vallen);
91         }
92     }
93     return header->response.cas;
94 }
95 
96 // Overload for multi-lookup responses
recv_subdoc_response( cb::mcbp::ClientOpcode expected_cmd, cb::mcbp::Status expected_status, const std::vector<SubdocMultiLookupResult>& expected_results)97 uint64_t recv_subdoc_response(
98         cb::mcbp::ClientOpcode expected_cmd,
99         cb::mcbp::Status expected_status,
100         const std::vector<SubdocMultiLookupResult>& expected_results) {
101     union {
102         protocol_binary_response_subdocument response;
103         char bytes[1024];
104     } receive;
105 
106     safe_recv_packet(receive.bytes, sizeof(receive.bytes));
107 
108     mcbp_validate_response_header(
109             (protocol_binary_response_no_extras*)&receive.response,
110             expected_cmd,
111             expected_status);
112 
113     // Decode body and check against expected_results
114     const auto& header = receive.response.message.header;
115     const char* val_ptr =
116             receive.bytes + sizeof(header) + header.response.getExtlen();
117     const size_t vallen =
118             header.response.getBodylen() + header.response.getExtlen();
119 
120     size_t offset = 0;
121     for (unsigned int ii = 0; ii < expected_results.size(); ii++) {
122         const size_t result_header_len = sizeof(uint16_t) + sizeof(uint32_t);
123         if (offset + result_header_len > vallen) {
124             ADD_FAILURE() << "Remaining value length too short for expected "
125                              "result header";
126             return -1;
127         }
128 
129         const auto& exp_result = expected_results[ii];
130         const char* result_header = val_ptr + offset;
131         uint16_t status =
132                 ntohs(*reinterpret_cast<const uint16_t*>(result_header));
133         EXPECT_EQ(exp_result.first, cb::mcbp::Status(status))
134                 << "Lookup result[" << ii << "]: status different";
135 
136         uint32_t result_len = ntohl(*reinterpret_cast<const uint32_t*>(
137                 result_header + sizeof(uint16_t)));
138         EXPECT_EQ(exp_result.second.size(), result_len)
139                 << "Lookup result[" << ii << "]: length different";
140 
141         if (offset + result_header_len + result_len > vallen) {
142             ADD_FAILURE() << "Remaining value length too short for expected "
143                              "result value";
144             return -1;
145         }
146 
147         std::string result_value(result_header + result_header_len, result_len);
148         EXPECT_EQ(exp_result.second, result_value) << "Lookup result[" << ii
149                                                    << "]: value differs";
150 
151         offset += result_header_len + result_len;
152     }
153 
154     return header.response.cas;
155 }
156 
157 // Allow GTest to print out std::vectors as part of EXPECT/ ASSERT error
158 // messages.
159 namespace std {
160 template <typename T>
operator <<(std::ostream& os, const std::vector<T>& v)161 std::ostream& operator<<(std::ostream& os, const std::vector<T>& v) {
162     os << '[';
163     for (auto& e : v) {
164         os << " " << e;
165     }
166     os << ']';
167     return os;
168 }
169 
170 // Specialization for uint8_t to print as hex.
171 template <>
operator <<(std::ostream& os, const std::vector<uint8_t>& v)172 std::ostream& operator<<(std::ostream& os, const std::vector<uint8_t>& v) {
173     os << '[' << std::hex;
174     for (auto& e : v) {
175         os << " " << std::setw(2) << std::setfill('0') << (e & 0xff);
176     }
177     os << ']';
178     return os;
179 }
180 } // namespace std
181 
182 // Overload for multi-mutation responses
recv_subdoc_response( cb::mcbp::ClientOpcode expected_cmd, cb::mcbp::Status expected_status, const std::vector<SubdocMultiMutationResult>& expected_results)183 uint64_t recv_subdoc_response(
184         cb::mcbp::ClientOpcode expected_cmd,
185         cb::mcbp::Status expected_status,
186         const std::vector<SubdocMultiMutationResult>& expected_results) {
187     union {
188         protocol_binary_response_subdocument response;
189         char bytes[1024];
190     } receive;
191 
192     safe_recv_packet(receive.bytes, sizeof(receive.bytes));
193 
194     mcbp_validate_response_header(
195             (protocol_binary_response_no_extras*)&receive.response,
196             expected_cmd,
197             expected_status);
198 
199     // TODO: Check extras for subdoc command and mutation / seqno (if enabled).
200 
201     // Decode body and check against expected_results
202     const auto& header = receive.response.message.header;
203     const char* val_ptr =
204             receive.bytes + sizeof(header) + header.response.getExtlen();
205     const size_t vallen =
206             header.response.getBodylen() - header.response.getExtlen();
207     std::string value(val_ptr, val_ptr + vallen);
208 
209     if (expected_status == cb::mcbp::Status::Success) {
210         if (enabled_hello_features.count(cb::mcbp::Feature::MUTATION_SEQNO) >
211             0) {
212             EXPECT_EQ(16, header.response.getExtlen());
213         } else {
214             EXPECT_EQ(0u, header.response.getExtlen());
215         }
216 
217         for (const auto& result : expected_results) {
218             // Should always have at least 7 bytes in result -
219             // index, status, resultlen.
220             EXPECT_GE(value.size(),
221                       sizeof(uint8_t) + sizeof(uint16_t) + sizeof(uint32_t));
222 
223             // Extract fields from result spec and validate.
224             uint8_t actual_index = *reinterpret_cast<uint8_t*>(&value[0]);
225             value.erase(value.begin());
226             EXPECT_EQ(result.index, actual_index);
227 
228             auto actual_status = cb::mcbp::Status(
229                     ntohs(*reinterpret_cast<uint16_t*>(&value[0])));
230             value.erase(value.begin(), value.begin() + 2);
231             EXPECT_EQ(result.status, actual_status);
232 
233             uint32_t actual_resultlen =
234                     ntohl(*reinterpret_cast<uint32_t*>(&value[0]));
235             value.erase(value.begin(), value.begin() + 4);
236             EXPECT_EQ(result.result.size(), actual_resultlen);
237 
238             std::string actual_result = value.substr(0, actual_resultlen);
239             value.erase(value.begin(), value.begin() + actual_resultlen);
240             EXPECT_EQ(result.result, actual_result);
241         }
242         // Should have consumed all of the value.
243         EXPECT_EQ(0u, value.size());
244 
245     } else if (expected_status == cb::mcbp::Status::SubdocMultiPathFailure) {
246         // Specific path failed - should have a 3-byte body containing
247         // specific status and index of first failing spec.
248         EXPECT_EQ(3, vallen) << "Incorrect value:'"
249                              << std::string(val_ptr, vallen) << '"';
250         uint8_t actual_fail_index = *val_ptr;
251         auto actual_fail_spec_status =
252                 cb::mcbp::Status(ntohs(*reinterpret_cast<const uint16_t*>(
253                         val_ptr + sizeof(actual_fail_index))));
254         EXPECT_EQ(1, expected_results.size());
255         EXPECT_EQ(expected_results[0].index, actual_fail_index);
256         EXPECT_EQ(expected_results[0].status, actual_fail_spec_status);
257     } else {
258         // Body must either be empty or contain an error context
259         if (vallen > 20) {
260             EXPECT_EQ(std::string("{\"error\":{\"context\":"),
261                       std::string(val_ptr, 20))
262                     << "If non-empty, body must contain an error context";
263         } else {
264             EXPECT_EQ(0, vallen)
265                     << "Body must be empty or contain an error context";
266         }
267     }
268 
269     return header.response.cas;
270 }
271 
subdoc_verify_cmd( const BinprotSubdocCommand& cmd, cb::mcbp::Status err, const std::string& value, BinprotSubdocResponse& resp)272 ::testing::AssertionResult SubdocTestappTest::subdoc_verify_cmd(
273         const BinprotSubdocCommand& cmd,
274         cb::mcbp::Status err,
275         const std::string& value,
276         BinprotSubdocResponse& resp) {
277     using ::testing::AssertionSuccess;
278     using ::testing::AssertionFailure;
279 
280     send_subdoc_cmd(cmd);
281     recv_subdoc_response(cmd, err, resp);
282     if (::testing::Test::HasFailure()) {
283         return AssertionFailure();
284     }
285 
286     if (!value.empty() && cmd.getOp() != cb::mcbp::ClientOpcode::SubdocExists) {
287         if (value != resp.getValue()) {
288             return AssertionFailure()
289                    << "Value mismatch for " << cmd << std::endl
290                    << "  Expected: " << value << std::endl
291                    << "  Got: " << resp.getValue() << std::endl;
292         }
293     }
294 
295     // Check datatype is JSON for commands which successfully return data; if
296     // the connection has negotiated JSON.
297     if (resp.isSuccess()) {
298         switch (cmd.getOp()) {
299         default:
300             // ignore
301             break;
302         case cb::mcbp::ClientOpcode::SubdocGet:
303         case cb::mcbp::ClientOpcode::SubdocCounter:
304         case cb::mcbp::ClientOpcode::SubdocGetCount:
305             if (cb::mcbp::Datatype(resp.getDatatype()) !=
306                 expectedJSONDatatype()) {
307                 return AssertionFailure()
308                        << "Datatype mismatch for " << cmd << " - expected:"
309                        << mcbp::datatype::to_string(protocol_binary_datatype_t(
310                                   expectedJSONDatatype()))
311                        << " actual:"
312                        << mcbp::datatype::to_string(resp.getDatatype());
313             }
314 
315             // Check that JSON means JSON
316             auto value = resp.getData();
317             cb::const_char_buffer str(
318                     reinterpret_cast<const char*>(value.data()), value.size());
319             if (!isJSON(str)) {
320                 return AssertionFailure()
321                        << "JSON validation failed for response data:'"
322                        << resp.getDataString() << "''";
323             }
324         }
325     }
326 
327     return AssertionSuccess();
328 }
329 
330 // Overload for multi-lookup commands.
expect_subdoc_cmd( const SubdocMultiLookupCmd& cmd, cb::mcbp::Status expected_status, const std::vector<SubdocMultiLookupResult>& expected_results)331 uint64_t expect_subdoc_cmd(
332         const SubdocMultiLookupCmd& cmd,
333         cb::mcbp::Status expected_status,
334         const std::vector<SubdocMultiLookupResult>& expected_results) {
335     std::vector<char> payload = cmd.encode();
336     safe_send(payload.data(), payload.size(), false);
337 
338     return recv_subdoc_response(cb::mcbp::ClientOpcode::SubdocMultiLookup,
339                                 expected_status,
340                                 expected_results);
341 }
342 
343 // Overload for multi-mutation commands.
expect_subdoc_cmd( const SubdocMultiMutationCmd& cmd, cb::mcbp::Status expected_status, const std::vector<SubdocMultiMutationResult>& expected_results)344 uint64_t expect_subdoc_cmd(
345         const SubdocMultiMutationCmd& cmd,
346         cb::mcbp::Status expected_status,
347         const std::vector<SubdocMultiMutationResult>& expected_results) {
348     std::vector<char> payload = cmd.encode();
349     safe_send(payload.data(), payload.size(), false);
350 
351     return recv_subdoc_response(cmd.command, expected_status, expected_results);
352 }
353