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 
30 class 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  */
43 class StoredDocKey : public DocKeyInterface<StoredDocKey> {
44 public:
45     /**
46      * Construct empty - required for some std containers
47      */
StoredDocKey()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      */
StoredDocKey(const uint8_t* key, size_t nkey, DocNamespace docNamespace)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      */
StoredDocKey(const DocKey& key)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      */
StoredDocKey(const std::string& key, DocNamespace docNamespace)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      */
StoredDocKey(const uint8_t* key, size_t nkey)92     StoredDocKey(const uint8_t* key, size_t nkey)
93         : keydata(reinterpret_cast<const char*>(key), nkey) {
94     }
95 
data() const96     const uint8_t* data() const {
97         return reinterpret_cast<const uint8_t*>(
98                 &keydata.data()[namespaceBytes]);
99     }
100 
size() const101     size_t size() const {
102         return keydata.size() - namespaceBytes;
103     }
104 
getDocNamespace() const105     DocNamespace getDocNamespace() const {
106         return DocNamespace(keydata[0]);
107     }
108 
c_str() const109     const char* c_str() const {
110         return &keydata.c_str()[namespaceBytes];
111     }
112 
compare(const StoredDocKey& rhs) const113     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      */
getDocNameSpacedData() const121     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      */
empty() const128     bool empty() const {
129         return keydata.empty();
130     }
131 
132     /**
133      * Return the size of the buffer returned by getDocNameSpacedData()
134      */
getDocNameSpacedSize() const135     size_t getDocNameSpacedSize() const {
136         return keydata.size();
137     }
138 
operator ==(const StoredDocKey& rhs) const139     bool operator==(const StoredDocKey& rhs) const {
140         return keydata == rhs.keydata;
141     }
142 
operator !=(const StoredDocKey& rhs) const143     bool operator!=(const StoredDocKey& rhs) const {
144         return !(*this == rhs);
145     }
146 
operator <(const StoredDocKey& rhs) const147     bool operator<(const StoredDocKey& rhs) const {
148         return keydata < rhs.keydata;
149     }
150 
151 protected:
152     static const size_t namespaceBytes = sizeof(DocNamespace);
153     std::string keydata;
154 };
155 
156 std::ostream& operator<<(std::ostream& os, const StoredDocKey& key);
157 
158 static_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  */
164 namespace std {
165 template <>
166 struct hash<StoredDocKey> {
operator ()std::hash167     std::size_t operator()(const StoredDocKey& key) const {
168         return key.hash();
169     }
170 };
171 }
172 
173 class MutationLogEntryV2;
174 class 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  */
187 class SerialisedDocKey : public DocKeyInterface<SerialisedDocKey> {
188 public:
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 
data() const195     const uint8_t* data() const {
196         return bytes;
197     }
198 
size() const199     size_t size() const {
200         return length;
201     }
202 
getDocNamespace() const203     DocNamespace getDocNamespace() const {
204         return docNamespace;
205     }
206 
operator ==(const DocKey rhs) const207     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      */
getObjectSize() const216     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      */
getObjectSize(const DocKey key)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 {
operator ()SerialisedDocKey::SerialisedDocKeyDelete234         void operator()(SerialisedDocKey* p) {
235             p->~SerialisedDocKey();
236             delete[] reinterpret_cast<uint8_t*>(p);
237         }
238     };
239 
make( const DocKey key)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 
249 protected:
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 
SerialisedDocKey()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      */
SerialisedDocKey(const DocKey key)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(key.size()) +
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      */
getObjectSize(size_t len)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 
293 std::ostream& operator<<(std::ostream& os, const SerialisedDocKey& key);
294 
295 static_assert(std::is_standard_layout<SerialisedDocKey>::value,
296               "SeralisedDocKey: must satisfy is_standard_layout");
297