1/*
2 *     Copyright 2016 Couchbase, Inc
3 *
4 *   Licensed under the Apache License, Version 2.0 (the "License");
5 *   you may not use this file except in compliance with the License.
6 *   You may obtain a copy of the License at
7 *
8 *       http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *   Unless required by applicable law or agreed to in writing, software
11 *   distributed under the License is distributed on an "AS IS" BASIS,
12 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 *   See the License for the specific language governing permissions and
14 *   limitations under the License.
15 */
16
17#pragma once
18
19#include <limits>
20#include <memory>
21#include <string>
22#include <type_traits>
23
24#include <memcached/dockey.h>
25
26#include <gsl/gsl>
27
28#include "ep_types.h"
29
30class SerialisedDocKey;
31
32/**
33 * StoredDocKey is a container for key data
34 *
35 * Internally an n byte key is stored in a n + 1 std::string.
36 *  a) We zero terminate so that data() is safe for printing as a c-string.
37 *  b) We store the DocNamespace in byte 0 (duplicated in parent class DocKey).
38 *    This is because StoredDocKey typically ends up being written to disk and
39 *    the DocNamespace forms part of the on-disk key. Pre-allocating space
40 *    for the DocNamespace means storage components don't have to create a
41 *    new buffer into which we layout DocNamespace and key data.
42 */
43class StoredDocKey : public DocKeyInterface<StoredDocKey> {
44public:
45    /**
46     * Construct empty - required for some std containers
47     */
48    StoredDocKey() : keydata() {
49    }
50
51    /**
52     * Create a StoredDocKey from key to key+nkey in docNamespace.
53     * @param key pointer to key data
54     * @param nkey byte length of key (which will be copied in)
55     * @docNamespace the namespace the key applies to
56     */
57    StoredDocKey(const uint8_t* key, size_t nkey, DocNamespace docNamespace) {
58        keydata.resize(nkey + namespaceBytes);
59        keydata.replace(
60                namespaceBytes, nkey, reinterpret_cast<const char*>(key), nkey);
61        keydata[0] = static_cast<std::underlying_type<DocNamespace>::type>(
62                docNamespace);
63    }
64
65    /**
66     * Create a StoredDocKey from a DocKey
67     * @param key DocKey that is to be copied-in
68     */
69    StoredDocKey(const DocKey& key)
70        : StoredDocKey(key.data(), key.size(), key.getDocNamespace()) {
71    }
72
73    /**
74     * Create a StoredDocKey from a std::string. Post
75     * construction size will return std::string::size();
76     * @param key std::string to be copied-in
77     * @param docNamespace the namespace that the key applies to
78     */
79    StoredDocKey(const std::string& key, DocNamespace docNamespace)
80        : StoredDocKey(reinterpret_cast<const uint8_t*>(key.c_str()),
81                       key.size(),
82                       docNamespace) {
83    }
84
85    /**
86     * Create a StoredDocKey from a buffer that was previously
87     * obtained from getNamespacedData()
88     * Post construction size() will return nkey - 1
89     * @param key pointer to data to be copied in
90     * @param nkey the number of bytes to be copied
91     */
92    StoredDocKey(const uint8_t* key, size_t nkey)
93        : keydata(reinterpret_cast<const char*>(key), nkey) {
94    }
95
96    const uint8_t* data() const {
97        return reinterpret_cast<const uint8_t*>(
98                &keydata.data()[namespaceBytes]);
99    }
100
101    size_t size() const {
102        return keydata.size() - namespaceBytes;
103    }
104
105    DocNamespace getDocNamespace() const {
106        return DocNamespace(keydata[0]);
107    }
108
109    const char* c_str() const {
110        return &keydata.c_str()[namespaceBytes];
111    }
112
113    int compare(const StoredDocKey& rhs) const {
114        return keydata.compare(rhs.keydata);
115    }
116
117    /**
118     * Return the key with the DocNamespace as a prefix.
119     * Thus the "beer::bud" in the Collections namespace becomes "\1beer::bud"
120     */
121    const uint8_t* getDocNameSpacedData() const {
122        return reinterpret_cast<const uint8_t*>(keydata.data());
123    }
124
125    /**
126     * @return true if StoredDocKey was constructed empty from StoredDocKey()
127     */
128    bool empty() const {
129        return keydata.empty();
130    }
131
132    /**
133     * Return the size of the buffer returned by getDocNameSpacedData()
134     */
135    size_t getDocNameSpacedSize() const {
136        return keydata.size();
137    }
138
139    bool operator==(const StoredDocKey& rhs) const {
140        return keydata == rhs.keydata;
141    }
142
143    bool operator!=(const StoredDocKey& rhs) const {
144        return !(*this == rhs);
145    }
146
147    bool operator<(const StoredDocKey& rhs) const {
148        return keydata < rhs.keydata;
149    }
150
151protected:
152    static const size_t namespaceBytes = sizeof(DocNamespace);
153    std::string keydata;
154};
155
156std::ostream& operator<<(std::ostream& os, const StoredDocKey& key);
157
158static_assert(sizeof(DocNamespace) == sizeof(uint8_t),
159              "StoredDocKey: DocNamespace is too large");
160
161/**
162 * A hash function for StoredDocKey so they can be used in std::map and friends.
163 */
164namespace std {
165template <>
166struct hash<StoredDocKey> {
167    std::size_t operator()(const StoredDocKey& key) const {
168        return key.hash();
169    }
170};
171}
172
173class MutationLogEntryV2;
174class StoredValue;
175
176/**
177 * SerialisedDocKey maintains the key data in an allocation that is not owned by
178 * the class. The class is essentially immutable, providing a "view" onto the
179 * larger block.
180 *
181 * For example where a StoredDocKey needs to exist as part of a bigger block of
182 * data, SerialisedDocKey is the class to use.
183 *
184 * A limited number of classes are friends and only those classes can construct
185 * a SerialisedDocKey.
186 */
187class SerialisedDocKey : public DocKeyInterface<SerialisedDocKey> {
188public:
189    /**
190     * The copy constructor is deleted due to the bytes living outside of the
191     * object.
192     */
193    SerialisedDocKey(const SerialisedDocKey& obj) = delete;
194
195    const uint8_t* data() const {
196        return bytes;
197    }
198
199    size_t size() const {
200        return length;
201    }
202
203    DocNamespace getDocNamespace() const {
204        return docNamespace;
205    }
206
207    bool operator==(const DocKey rhs) const {
208        return size() == rhs.size() &&
209               getDocNamespace() == rhs.getDocNamespace() &&
210               std::memcmp(data(), rhs.data(), size()) == 0;
211    }
212
213    /**
214     * Return how many bytes are (or need to be) allocated to this object
215     */
216    size_t getObjectSize() const {
217        return getObjectSize(length);
218    }
219
220    /**
221     * Return how many bytes are needed to store the DocKey
222     * @param key a DocKey that needs to be stored in a SerialisedDocKey
223     */
224    static size_t getObjectSize(const DocKey key) {
225        return getObjectSize(key.size());
226    }
227
228    /**
229     * Create a SerialisedDocKey and return a unique_ptr to the object.
230     * Note that the allocation is bigger than sizeof(SerialisedDocKey)
231     * @param key a DocKey to be stored as a SerialisedDocKey
232     */
233    struct SerialisedDocKeyDelete {
234        void operator()(SerialisedDocKey* p) {
235            p->~SerialisedDocKey();
236            delete[] reinterpret_cast<uint8_t*>(p);
237        }
238    };
239
240    static std::unique_ptr<SerialisedDocKey, SerialisedDocKeyDelete> make(
241            const DocKey key) {
242        std::unique_ptr<SerialisedDocKey, SerialisedDocKeyDelete> rval(
243                reinterpret_cast<SerialisedDocKey*>(
244                        new uint8_t[getObjectSize(key)]));
245        new (rval.get()) SerialisedDocKey(key);
246        return rval;
247    }
248
249protected:
250    /**
251     * These following classes are "white-listed". They know how to allocate
252     * and construct this object so are allowed access to the constructor.
253     */
254    friend class MutationLogEntryV2;
255    friend class StoredValue;
256
257    SerialisedDocKey() : length(0), docNamespace(), bytes() {
258    }
259
260    /**
261     * Create a SerialisedDocKey from a DocKey. Protected constructor as this
262     * must be used by friends who know how to pre-allocate the object storage.
263     * throws length_error if DocKey::len exceeds 255
264     * @param key a DocKey to be stored
265     */
266    SerialisedDocKey(const DocKey key)
267        : length(0), docNamespace(key.getDocNamespace()) {
268        if (key.size() > std::numeric_limits<uint8_t>::max()) {
269            throw std::length_error(
270                    "SerialisedDocKey(const DocKey key) " +
271                    std::to_string(length) +
272                    "exceeds std::numeric_limits<uint8_t>::max()");
273        }
274        length = gsl::narrow_cast<uint8_t>(key.size());
275        // Copy the data into bytes, which should be allocated into a larger
276        // buffer.
277        std::copy(key.data(), key.data() + key.size(), bytes);
278    }
279
280    /**
281     * Returns the size in bytes of this object - fixed size plus the variable
282     * length for the bytes making up the key.
283     */
284    static size_t getObjectSize(size_t len) {
285        return sizeof(SerialisedDocKey) + len - sizeof(SerialisedDocKey().bytes);
286    }
287
288    uint8_t length;
289    DocNamespace docNamespace;
290    uint8_t bytes[1];
291};
292
293std::ostream& operator<<(std::ostream& os, const SerialisedDocKey& key);
294
295static_assert(std::is_standard_layout<SerialisedDocKey>::value,
296              "SeralisedDocKey: must satisfy is_standard_layout");
297