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:" << memcached_opcode_2_text(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, protocol_binary_response_status err, BinprotSubdocResponse& resp)40 static void recv_subdoc_response(const BinprotSubdocCommand& cmd,
41                                  protocol_binary_response_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(protocol_binary_command expected_cmd, protocol_binary_response_status expected_status, const std::string& expected_value)56 uint64_t recv_subdoc_response(protocol_binary_command expected_cmd,
57                               protocol_binary_response_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.extlen;
79     const size_t vallen =
80             header->response.getBodylen() - header->response.extlen;
81 
82     if (!expected_value.empty() &&
83         (expected_cmd != PROTOCOL_BINARY_CMD_SUBDOC_EXISTS)) {
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.status == PROTOCOL_BINARY_RESPONSE_SUCCESS) {
90             EXPECT_EQ(0u, vallen);
91         }
92     }
93     return header->response.cas;
94 }
95 
96 // Overload for multi-lookup responses
recv_subdoc_response( protocol_binary_command expected_cmd, protocol_binary_response_status expected_status, const std::vector<SubdocMultiLookupResult>& expected_results)97 uint64_t recv_subdoc_response(
98         protocol_binary_command expected_cmd,
99         protocol_binary_response_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.extlen;
117     const size_t vallen = header.response.getBodylen() + header.response.extlen;
118 
119     size_t offset = 0;
120     for (unsigned int ii = 0; ii < expected_results.size(); ii++) {
121         const size_t result_header_len = sizeof(uint16_t) + sizeof(uint32_t);
122         if (offset + result_header_len > vallen) {
123             ADD_FAILURE() << "Remaining value length too short for expected "
124                              "result header";
125             return -1;
126         }
127 
128         const auto& exp_result = expected_results[ii];
129         const char* result_header = val_ptr + offset;
130         uint16_t status =
131                 ntohs(*reinterpret_cast<const uint16_t*>(result_header));
132         EXPECT_EQ(exp_result.first, protocol_binary_response_status(status))
133                 << "Lookup result[" << ii << "]: status different";
134 
135         uint32_t result_len = ntohl(*reinterpret_cast<const uint32_t*>(
136                 result_header + sizeof(uint16_t)));
137         EXPECT_EQ(exp_result.second.size(), result_len)
138                 << "Lookup result[" << ii << "]: length different";
139 
140         if (offset + result_header_len + result_len > vallen) {
141             ADD_FAILURE() << "Remaining value length too short for expected "
142                              "result value";
143             return -1;
144         }
145 
146         std::string result_value(result_header + result_header_len, result_len);
147         EXPECT_EQ(exp_result.second, result_value) << "Lookup result[" << ii
148                                                    << "]: value differs";
149 
150         offset += result_header_len + result_len;
151     }
152 
153     return header.response.cas;
154 }
155 
156 // Allow GTest to print out std::vectors as part of EXPECT/ ASSERT error
157 // messages.
158 namespace std {
159 template <typename T>
operator <<(std::ostream& os, const std::vector<T>& v)160 std::ostream& operator<<(std::ostream& os, const std::vector<T>& v) {
161     os << '[';
162     for (auto& e : v) {
163         os << " " << e;
164     }
165     os << ']';
166     return os;
167 }
168 
169 // Specialization for uint8_t to print as hex.
170 template <>
operator <<(std::ostream& os, const std::vector<uint8_t>& v)171 std::ostream& operator<<(std::ostream& os, const std::vector<uint8_t>& v) {
172     os << '[' << std::hex;
173     for (auto& e : v) {
174         os << " " << std::setw(2) << std::setfill('0') << (e & 0xff);
175     }
176     os << ']';
177     return os;
178 }
179 } // namespace std
180 
181 // Overload for multi-mutation responses
recv_subdoc_response( protocol_binary_command expected_cmd, protocol_binary_response_status expected_status, const std::vector<SubdocMultiMutationResult>& expected_results)182 uint64_t recv_subdoc_response(
183         protocol_binary_command expected_cmd,
184         protocol_binary_response_status expected_status,
185         const std::vector<SubdocMultiMutationResult>& expected_results) {
186     union {
187         protocol_binary_response_subdocument response;
188         char bytes[1024];
189     } receive;
190 
191     safe_recv_packet(receive.bytes, sizeof(receive.bytes));
192 
193     mcbp_validate_response_header(
194             (protocol_binary_response_no_extras*)&receive.response,
195             expected_cmd,
196             expected_status);
197 
198     // TODO: Check extras for subdoc command and mutation / seqno (if enabled).
199 
200     // Decode body and check against expected_results
201     const auto& header = receive.response.message.header;
202     const char* val_ptr =
203             receive.bytes + sizeof(header) + header.response.extlen;
204     const size_t vallen = header.response.getBodylen() - header.response.extlen;
205     std::string value(val_ptr, val_ptr + vallen);
206 
207     if (expected_status == PROTOCOL_BINARY_RESPONSE_SUCCESS) {
208         if (enabled_hello_features.count(cb::mcbp::Feature::MUTATION_SEQNO) >
209             0) {
210             EXPECT_EQ(16, header.response.extlen);
211         } else {
212             EXPECT_EQ(0u, header.response.extlen);
213         }
214 
215         for (const auto& result : expected_results) {
216             // Should always have at least 7 bytes in result -
217             // index, status, resultlen.
218             EXPECT_GE(value.size(),
219                       sizeof(uint8_t) + sizeof(uint16_t) + sizeof(uint32_t));
220 
221             // Extract fields from result spec and validate.
222             uint8_t actual_index = *reinterpret_cast<uint8_t*>(&value[0]);
223             value.erase(value.begin());
224             EXPECT_EQ(result.index, actual_index);
225 
226             uint16_t actual_status =
227                     ntohs(*reinterpret_cast<uint16_t*>(&value[0]));
228             value.erase(value.begin(), value.begin() + 2);
229             EXPECT_EQ(result.status, actual_status);
230 
231             uint32_t actual_resultlen =
232                     ntohl(*reinterpret_cast<uint32_t*>(&value[0]));
233             value.erase(value.begin(), value.begin() + 4);
234             EXPECT_EQ(result.result.size(), actual_resultlen);
235 
236             std::string actual_result = value.substr(0, actual_resultlen);
237             value.erase(value.begin(), value.begin() + actual_resultlen);
238             EXPECT_EQ(result.result, actual_result);
239         }
240         // Should have consumed all of the value.
241         EXPECT_EQ(0u, value.size());
242 
243     } else if (expected_status ==
244                PROTOCOL_BINARY_RESPONSE_SUBDOC_MULTI_PATH_FAILURE) {
245         // Specific path failed - should have a 3-byte body containing
246         // specific status and index of first failing spec.
247         EXPECT_EQ(3, vallen) << "Incorrect value:'"
248                              << std::string(val_ptr, vallen) << '"';
249         uint8_t actual_fail_index = *val_ptr;
250         uint16_t actual_fail_spec_status =
251                 ntohs(*reinterpret_cast<const uint16_t*>(
252                         val_ptr + sizeof(actual_fail_index)));
253         EXPECT_EQ(1, expected_results.size());
254         EXPECT_EQ(expected_results[0].index, actual_fail_index);
255         EXPECT_EQ(expected_results[0].status, actual_fail_spec_status);
256     } else {
257         // Top-level error - should have zero body.
258         EXPECT_EQ(0u, vallen);
259     }
260 
261     return header.response.cas;
262 }
263 
subdoc_verify_cmd( const BinprotSubdocCommand& cmd, protocol_binary_response_status err, const std::string& value, BinprotSubdocResponse& resp)264 ::testing::AssertionResult SubdocTestappTest::subdoc_verify_cmd(
265         const BinprotSubdocCommand& cmd,
266         protocol_binary_response_status err,
267         const std::string& value,
268         BinprotSubdocResponse& resp) {
269     using ::testing::AssertionSuccess;
270     using ::testing::AssertionFailure;
271 
272     send_subdoc_cmd(cmd);
273     recv_subdoc_response(cmd, err, resp);
274     if (::testing::Test::HasFailure()) {
275         return AssertionFailure();
276     }
277 
278     if (!value.empty() && cmd.getOp() != PROTOCOL_BINARY_CMD_SUBDOC_EXISTS) {
279         if (value != resp.getValue()) {
280             return AssertionFailure()
281                    << "Value mismatch for " << cmd << std::endl
282                    << "  Expected: " << value << std::endl
283                    << "  Got: " << resp.getValue() << std::endl;
284         }
285     }
286 
287     // Check datatype is JSON for commands which successfully return data; if
288     // the connection has negotiated JSON.
289     if (resp.isSuccess()) {
290         switch (cmd.getOp()) {
291         case PROTOCOL_BINARY_CMD_SUBDOC_GET:
292         case PROTOCOL_BINARY_CMD_SUBDOC_COUNTER:
293         case PROTOCOL_BINARY_CMD_SUBDOC_GET_COUNT:
294             if (cb::mcbp::Datatype(resp.getDatatype()) !=
295                 expectedJSONDatatype()) {
296                 return AssertionFailure()
297                        << "Datatype mismatch for " << cmd << " - expected:"
298                        << mcbp::datatype::to_string(protocol_binary_datatype_t(
299                                   expectedJSONDatatype()))
300                        << " actual:"
301                        << mcbp::datatype::to_string(resp.getDatatype());
302             }
303 
304             // Check that JSON means JSON
305             auto value = resp.getData();
306             cb::const_char_buffer str(
307                     reinterpret_cast<const char*>(value.data()), value.size());
308             if (!isJSON(str)) {
309                 return AssertionFailure()
310                        << "JSON validation failed for response data:'"
311                        << resp.getDataString() << "''";
312             }
313         }
314     }
315 
316     return AssertionSuccess();
317 }
318 
319 // Overload for multi-lookup commands.
expect_subdoc_cmd( const SubdocMultiLookupCmd& cmd, protocol_binary_response_status expected_status, const std::vector<SubdocMultiLookupResult>& expected_results)320 uint64_t expect_subdoc_cmd(
321         const SubdocMultiLookupCmd& cmd,
322         protocol_binary_response_status expected_status,
323         const std::vector<SubdocMultiLookupResult>& expected_results) {
324     std::vector<char> payload = cmd.encode();
325     safe_send(payload.data(), payload.size(), false);
326 
327     return recv_subdoc_response(PROTOCOL_BINARY_CMD_SUBDOC_MULTI_LOOKUP,
328                                 expected_status,
329                                 expected_results);
330 }
331 
332 // Overload for multi-mutation commands.
expect_subdoc_cmd( const SubdocMultiMutationCmd& cmd, protocol_binary_response_status expected_status, const std::vector<SubdocMultiMutationResult>& expected_results)333 uint64_t expect_subdoc_cmd(
334         const SubdocMultiMutationCmd& cmd,
335         protocol_binary_response_status expected_status,
336         const std::vector<SubdocMultiMutationResult>& expected_results) {
337     std::vector<char> payload = cmd.encode();
338     safe_send(payload.data(), payload.size(), false);
339 
340     return recv_subdoc_response(cmd.command, expected_status, expected_results);
341 }
342