1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 *     Copyright 2016 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#include "get_context.h"
18
19#include "engine_wrapper.h"
20#include "trace.h"
21
22#include <daemon/buckets.h>
23#include <daemon/debug_helpers.h>
24#include <daemon/mcaudit.h>
25#include <daemon/mcbp.h>
26#include <daemon/memcached.h>
27#include <xattr/utils.h>
28#include <gsl/gsl>
29
30ENGINE_ERROR_CODE GetCommandContext::getItem() {
31    const auto key = cookie.getRequestKey();
32    auto ret = bucket_get(cookie, key, vbucket);
33    if (ret.first == cb::engine_errc::success) {
34        it = std::move(ret.second);
35        if (!bucket_get_item_info(cookie, it.get(), &info)) {
36            LOG_WARNING("{}: Failed to get item info", connection.getId());
37            return ENGINE_FAILED;
38        }
39
40        payload.buf = static_cast<const char*>(info.value[0].iov_base);
41        payload.len = info.value[0].iov_len;
42
43        bool need_inflate = false;
44        if (mcbp::datatype::is_snappy(info.datatype)) {
45            need_inflate = mcbp::datatype::is_xattr(info.datatype) ||
46                           !connection.isSnappyEnabled();
47        }
48
49        if (need_inflate) {
50            state = State::InflateItem;
51        } else {
52            state = State::SendResponse;
53        }
54    } else if (ret.first == cb::engine_errc::no_such_key) {
55        state = State::NoSuchItem;
56        ret.first = cb::engine_errc::success;
57    }
58
59    return ENGINE_ERROR_CODE(ret.first);
60}
61
62ENGINE_ERROR_CODE GetCommandContext::inflateItem() {
63    try {
64        if (!cb::compression::inflate(cb::compression::Algorithm::Snappy,
65                                      payload, buffer)) {
66            LOG_WARNING("{}: Failed to inflate item", connection.getId());
67            return ENGINE_FAILED;
68        }
69        payload = buffer;
70        info.datatype &= ~PROTOCOL_BINARY_DATATYPE_SNAPPY;
71    } catch (const std::bad_alloc&) {
72        return ENGINE_ENOMEM;
73    }
74
75    state = State::SendResponse;
76    return ENGINE_SUCCESS;
77}
78
79ENGINE_ERROR_CODE GetCommandContext::sendResponse() {
80    if (mcbp::datatype::is_xattr(info.datatype)) {
81        payload = cb::xattr::get_body(payload);
82        info.datatype &= ~PROTOCOL_BINARY_DATATYPE_XATTR;
83    }
84
85    info.datatype = connection.getEnabledDatatypes(info.datatype);
86
87    uint16_t keylen = 0;
88    uint32_t bodylen = gsl::narrow<uint32_t>(sizeof(info.flags) + payload.len);
89
90    if (shouldSendKey()) {
91        keylen = uint16_t(info.nkey);
92        bodylen += keylen;
93    }
94
95    // Set the CAS to add into the header
96    cookie.setCas(info.cas);
97    mcbp_add_header(cookie,
98                    PROTOCOL_BINARY_RESPONSE_SUCCESS,
99                    sizeof(info.flags),
100                    keylen,
101                    bodylen,
102                    info.datatype);
103
104    // Add the flags
105    connection.addIov(&info.flags, sizeof(info.flags));
106
107    // Add the value
108    if (shouldSendKey()) {
109        connection.addIov(info.key, info.nkey);
110    }
111
112    connection.addIov(payload.buf, payload.len);
113    connection.setState(McbpStateMachine::State::send_data);
114    cb::audit::document::add(cookie, cb::audit::document::Operation::Read);
115
116    STATS_HIT(&connection, get);
117    update_topkeys(cookie);
118
119    state = State::Done;
120    return ENGINE_SUCCESS;
121}
122
123ENGINE_ERROR_CODE GetCommandContext::noSuchItem() {
124    STATS_MISS(&connection, get);
125
126    const auto key = cookie.getRequestKey();
127    MEMCACHED_COMMAND_GET(connection.getId(),
128                          reinterpret_cast<const char*>(key.data()),
129                          int(key.size()), -1, 0);
130
131    if (cookie.getRequest().isQuiet()) {
132        ++connection.getBucket()
133                  .responseCounters[PROTOCOL_BINARY_RESPONSE_KEY_ENOENT];
134        connection.setState(McbpStateMachine::State::new_cmd);
135    } else {
136        if (shouldSendKey()) {
137            cookie.sendResponse(
138                    cb::mcbp::Status::KeyEnoent,
139                    {},
140                    {reinterpret_cast<const char*>(key.data()), key.size()},
141                    {},
142                    cb::mcbp::Datatype::Raw,
143                    0);
144        } else {
145            cookie.sendResponse(cb::mcbp::Status::KeyEnoent);
146        }
147    }
148
149    state = State::Done;
150    return ENGINE_SUCCESS;
151}
152
153ENGINE_ERROR_CODE GetCommandContext::step() {
154    auto ret = ENGINE_SUCCESS;
155    do {
156        switch (state) {
157        case State::GetItem:
158            ret = getItem();
159            break;
160        case State::NoSuchItem:
161            ret = noSuchItem();
162            break;
163        case State::InflateItem:
164            ret = inflateItem();
165            break;
166        case State::SendResponse:
167            ret = sendResponse();
168            break;
169        case State::Done:
170            return ENGINE_SUCCESS;
171        }
172    } while (ret == ENGINE_SUCCESS);
173
174    return ret;
175}
176