MB-21599: Prevent LWW XDCR from non LWW cluster 16/70016/12
authorJim Walker <jim@couchbase.com>
Thu, 17 Nov 2016 12:35:43 +0000 (12:35 +0000)
committerDave Rigby <daver@couchbase.com>
Mon, 21 Nov 2016 17:08:00 +0000 (17:08 +0000)
Add support for FORCE_ACCEPT_WITH_META_OPS
 - This flag must be set if the bucket is lww
 - Error if the bucket is !lww

Add support for REGENERATE_CAS
 - This flag requires SKIP_CONFLICT_RESOLUTION
 - When the item is stored, its CAS is re-created.

Tidy up test code relating to *_with_meta and add more extensive
testing of options and nmeta.

Change-Id: Ifb149927d3f63357d30392352c9c81533cbf2ff1
Reviewed-on: http://review.couchbase.org/70016
Tested-by: buildbot <build@couchbase.com>
Reviewed-by: Dave Rigby <daver@couchbase.com>
src/dcp/stream.cc
src/ep.cc
src/ep.h
src/ep_engine.cc
src/ep_engine.h
src/tapconnection.cc
tests/ep_test_apis.cc
tests/ep_test_apis.h
tests/ep_testsuite.cc
tests/ep_testsuite_xdcr.cc

index 6550e5a..2dbc973 100644 (file)
@@ -1704,7 +1704,9 @@ ENGINE_ERROR_CODE PassiveStream::processMutation(MutationResponse* mutation) {
     } else {
         ret = engine->getEpStore()->setWithMeta(*mutation->getItem(), 0, NULL,
                                                 consumer->getCookie(), true,
-                                                true, INITIAL_NRU_VALUE, false,
+                                                true, INITIAL_NRU_VALUE,
+                                                GenerateBySeqno::No,
+                                                GenerateCas::No,
                                                 mutation->getExtMetaData(),
                                                 true);
     }
@@ -1757,7 +1759,9 @@ ENGINE_ERROR_CODE PassiveStream::processDeletion(MutationResponse* deletion) {
                                                &delCas, NULL, deletion->getVBucket(),
                                                consumer->getCookie(), true,
                                                &meta, vb->isBackfillPhase(),
-                                               false, deletion->getBySeqno(),
+                                               GenerateBySeqno::No,
+                                               GenerateCas::No,
+                                               deletion->getBySeqno(),
                                                deletion->getExtMetaData(),
                                                true);
     if (ret == ENGINE_KEY_ENOENT) {
index 844f678..4413776 100644 (file)
--- a/src/ep.cc
+++ b/src/ep.cc
@@ -2104,7 +2104,8 @@ ENGINE_ERROR_CODE EventuallyPersistentStore::setWithMeta(
                                                      bool force,
                                                      bool allowExisting,
                                                      uint8_t nru,
-                                                     bool genBySeqno,
+                                                     GenerateBySeqno genBySeqno,
+                                                     GenerateCas genCas,
                                                      ExtendedMetaData *emd,
                                                      bool isReplication)
 {
@@ -2195,9 +2196,7 @@ ENGINE_ERROR_CODE EventuallyPersistentStore::setWithMeta(
     case WAS_DIRTY:
     case WAS_CLEAN:
         vb->setMaxCasAndTrackDrift(v->getCas());
-        queueDirty(vb, v, &lh, seqno,
-                   genBySeqno ? GenerateBySeqno::Yes : GenerateBySeqno::No,
-                   GenerateCas::No);
+        queueDirty(vb, v, &lh, seqno, genBySeqno, genCas);
         break;
     case NOT_FOUND:
         ret = ENGINE_KEY_ENOENT;
@@ -2751,7 +2750,8 @@ ENGINE_ERROR_CODE EventuallyPersistentStore::deleteWithMeta(
                                                      bool force,
                                                      ItemMetaData *itemMeta,
                                                      bool tapBackfill,
-                                                     bool genBySeqno,
+                                                     GenerateBySeqno genBySeqno,
+                                                     GenerateCas generateCas,
                                                      uint64_t bySeqno,
                                                      ExtendedMetaData *emd,
                                                      bool isReplication)
@@ -2865,19 +2865,16 @@ ENGINE_ERROR_CODE EventuallyPersistentStore::deleteWithMeta(
         break;
     case WAS_DIRTY:
     case WAS_CLEAN:
-        if (!genBySeqno) {
+        if (genBySeqno == GenerateBySeqno::No) {
             v->setBySeqno(bySeqno);
         }
 
         vb->setMaxCasAndTrackDrift(v->getCas());
 
         if (tapBackfill) {
-            tapQueueDirty(*vb, v, lh, seqno,
-                          genBySeqno ? GenerateBySeqno::Yes : GenerateBySeqno::No);
+            tapQueueDirty(*vb, v, lh, seqno, genBySeqno);
         } else {
-            queueDirty(vb, v, &lh, seqno,
-                       genBySeqno ? GenerateBySeqno::Yes : GenerateBySeqno::No,
-                       GenerateCas::No);
+            queueDirty(vb, v, &lh, seqno, genBySeqno, generateCas);
         }
         break;
     case NEED_BG_FETCH:
index 0aa8c2e..fe321e9 100644 (file)
--- a/src/ep.h
+++ b/src/ep.h
@@ -334,7 +334,8 @@ public:
                                   bool force,
                                   bool allowExisting,
                                   uint8_t nru = 0xff,
-                                  bool genBySeqno = true,
+                                  GenerateBySeqno genBySeqno = GenerateBySeqno::Yes,
+                                  GenerateCas genCas = GenerateCas::No,
                                   ExtendedMetaData *emd = NULL,
                                   bool isReplication = false);
 
@@ -407,11 +408,12 @@ public:
                                      const void *cookie,
                                      bool force,
                                      ItemMetaData *itemMeta,
-                                     bool tapBackfill=false,
-                                     bool genBySeqno=true,
-                                     uint64_t bySeqno=0,
-                                     ExtendedMetaData *emd = NULL,
-                                     bool isReplication=false);
+                                     bool tapBackfill,
+                                     GenerateBySeqno genBySeqno,
+                                     GenerateCas generateCas,
+                                     uint64_t bySeqno,
+                                     ExtendedMetaData *emd,
+                                     bool isReplication);
 
     void reset();
 
index 0f125f0..064c237 100644 (file)
@@ -5417,6 +5417,49 @@ ENGINE_ERROR_CODE EventuallyPersistentEngine::getMeta(const void* cookie,
     return rv;
 }
 
+protocol_binary_response_status EventuallyPersistentEngine::decodeWithMetaOptions(
+                              protocol_binary_request_delete_with_meta* request,
+                              GenerateCas& generateCas,
+                              bool& skipConflictResolution,
+                              int& keyOffset) {
+    uint8_t extlen = request->message.header.request.extlen;
+    keyOffset = 0;
+    bool forceFlag = false;
+    if (extlen == 28 || extlen == 30) {
+        uint32_t options;
+        memcpy(&options, request->bytes + sizeof(request->bytes),
+               sizeof(options));
+        options = ntohl(options);
+        keyOffset = 4; // 4 bytes for options
+
+        if (options & SKIP_CONFLICT_RESOLUTION_FLAG) {
+            skipConflictResolution = true;
+        }
+
+        if (options & FORCE_ACCEPT_WITH_META_OPS) {
+            forceFlag = true;
+        }
+
+        if (options & REGENERATE_CAS) {
+            generateCas = GenerateCas::Yes;
+        }
+    }
+
+    // Validate options
+    // If regenerate CAS then skip conflict resolution must be set
+    // If LWW force must be set
+    // If !LWW then force cannot be set).
+    if ((generateCas == GenerateCas::Yes && !skipConflictResolution) ||
+        (configuration.getConflictResolutionType() == "lww" && !forceFlag &&
+         generateCas == GenerateCas::No) ||
+        (forceFlag && (configuration.getConflictResolutionType() != "lww"))) {
+        return PROTOCOL_BINARY_RESPONSE_EINVAL;
+    }
+
+
+    return PROTOCOL_BINARY_RESPONSE_SUCCESS;
+}
+
 ENGINE_ERROR_CODE EventuallyPersistentEngine::setWithMeta(const void* cookie,
                                 protocol_binary_request_set_with_meta *request,
                                 ADD_RESPONSE response)
@@ -5424,9 +5467,13 @@ ENGINE_ERROR_CODE EventuallyPersistentEngine::setWithMeta(const void* cookie,
     // revid_nbytes, flags and exptime is mandatory fields.. and we need a key
     uint8_t extlen = request->message.header.request.extlen;
     uint16_t keylen = ntohs(request->message.header.request.keylen);
-    if ((extlen != 24           // Packet without nmeta or options
-         && extlen != 28        // Packet with options but without nmeta
-         && extlen != 26)       // Packet with nmeta
+    // extlen, the size dicates what is encoded.
+    // 24 = no nmeta and no options
+    // 26 = nmeta
+    // 28 = options (4-byte field)
+    // 30 = options and nmeta (options followed by nmeta)
+    // so 27, 25 etc... are illegal
+    if ((extlen != 24 && extlen != 26 && extlen != 28  && extlen != 30)
         || keylen == 0) {
         return sendResponse(response, NULL, 0, NULL, 0, NULL, 0,
                             PROTOCOL_BINARY_RAW_BYTES,
@@ -5452,39 +5499,39 @@ ENGINE_ERROR_CODE EventuallyPersistentEngine::setWithMeta(const void* cookie,
     uint64_t seqno = ntohll(request->message.body.seqno);
     uint64_t cas = ntohll(request->message.body.cas);
 
-    bool force = false;
-    ExtendedMetaData *emd = NULL;
+    bool skipConflictResolution = false;
+    GenerateCas generateCas = GenerateCas::No;
+    std::unique_ptr<ExtendedMetaData> emd;
+    int keyOffset = 0;
+    protocol_binary_response_status error = PROTOCOL_BINARY_RESPONSE_SUCCESS;
+    if ((error = decodeWithMetaOptions(request, generateCas,
+                                       skipConflictResolution, keyOffset)) !=
+        PROTOCOL_BINARY_RESPONSE_SUCCESS) {
+        return sendResponse(response, NULL, 0, NULL, 0, NULL, 0,
+                            PROTOCOL_BINARY_RAW_BYTES, error, 0, cookie);
+    }
 
-    if (extlen == 28) {
-        uint32_t options;
-        memcpy(&options, request->bytes + sizeof(request->bytes),
-               sizeof(options));
-        key += 4;       // 4 bytes for options
-        if (ntohl(options) & SKIP_CONFLICT_RESOLUTION_FLAG) {
-            force = true;
-        }
-    } else if (extlen == 26) {
-        uint16_t nmeta;
-        memcpy(&nmeta, request->bytes + sizeof(request->bytes),
-               sizeof(nmeta));
-        key += 2;       // 2 bytes for nmeta
+    if (extlen == 26 || extlen == 30) {
+        uint16_t nmeta = 0;
+        memcpy(&nmeta, key + keyOffset, sizeof(nmeta));
+        keyOffset += 2; // 2 bytes for nmeta
         nmeta = ntohs(nmeta);
         if (nmeta > 0) {
             // Correct the vallen
             vallen -= nmeta;
-            emd = new ExtendedMetaData(key + keylen + vallen, nmeta);
-
-            if (emd == NULL) {
+            try {
+                emd.reset(new ExtendedMetaData(key + keylen + keyOffset + vallen,
+                                               nmeta));
+            } catch (const std::bad_alloc&) {
                 return sendResponse(response, NULL, 0, NULL, 0, NULL, 0,
-                                    PROTOCOL_BINARY_RAW_BYTES,
-                                    PROTOCOL_BINARY_RESPONSE_ENOMEM, 0, cookie);
+                            PROTOCOL_BINARY_RAW_BYTES,
+                            PROTOCOL_BINARY_RESPONSE_ENOMEM, 0, cookie);
             }
 
             if (emd->getStatus() == ENGINE_EINVAL) {
-                delete emd;
                 return sendResponse(response, NULL, 0, NULL, 0, NULL, 0,
-                                    PROTOCOL_BINARY_RAW_BYTES,
-                                    PROTOCOL_BINARY_RESPONSE_EINVAL, 0, cookie);
+                            PROTOCOL_BINARY_RAW_BYTES,
+                            PROTOCOL_BINARY_RESPONSE_EINVAL, 0, cookie);
             }
         }
     }
@@ -5498,7 +5545,7 @@ ENGINE_ERROR_CODE EventuallyPersistentEngine::setWithMeta(const void* cookie,
                             PROTOCOL_BINARY_RESPONSE_E2BIG, 0, cookie);
     }
 
-    uint8_t *dta = key + keylen;
+    uint8_t* dta = key + keyOffset + keylen;
 
     if (!isDatatypeSupported(cookie)) {
         const int len = vallen;
@@ -5511,7 +5558,7 @@ ENGINE_ERROR_CODE EventuallyPersistentEngine::setWithMeta(const void* cookie,
     uint8_t ext_meta[1];
     uint8_t ext_len = EXT_META_LEN;
     *(ext_meta) = datatype;
-    Item *itm = new Item(key, keylen, flags, expiration, dta, vallen,
+    Item *itm = new Item(key + keyOffset, keylen, flags, expiration, dta, vallen,
                          ext_meta, ext_len, cas, -1, vbucket);
 
     if (itm == NULL) {
@@ -5539,11 +5586,12 @@ ENGINE_ERROR_CODE EventuallyPersistentEngine::setWithMeta(const void* cookie,
 
     ENGINE_ERROR_CODE ret = epstore->setWithMeta(*itm, ntohll(request->
                                                  message.header.request.cas),
-                                                 &by_seqno, cookie, force,
-                                                 allowExisting, 0xff, true,
-                                                 emd);
-
-    delete emd;
+                                                 &by_seqno, cookie,
+                                                 skipConflictResolution,
+                                                 allowExisting, 0xff,
+                                                 GenerateBySeqno::Yes,
+                                                 generateCas,
+                                                 emd.get(), false);
 
     if (ret == ENGINE_SUCCESS) {
         ++stats.numOpsSetMeta;
@@ -5609,9 +5657,13 @@ ENGINE_ERROR_CODE EventuallyPersistentEngine::deleteWithMeta(
     // revid_nbytes, flags and exptime is mandatory fields.. and we need a key
     uint16_t nkey = ntohs(request->message.header.request.keylen);
     uint8_t extlen = request->message.header.request.extlen;
-    if ((extlen != 24           // Packet without nmeta or options
-         && extlen != 28        // Packet with options but without nmeta
-         && extlen != 26)       // Packet with nmeta
+    // extlen, the size dicates what is encoded.
+    // 24 = no nmeta and no options
+    // 26 = nmeta
+    // 28 = options (4-byte field)
+    // 30 = options and nmeta (options followed by nmeta)
+    // so 27, 25 etc... are illegal
+    if ((extlen != 24 && extlen != 26 && extlen != 28  && extlen != 30)
         || nkey == 0) {
         return sendResponse(response, NULL, 0, NULL, 0, NULL, 0,
                             PROTOCOL_BINARY_RAW_BYTES,
@@ -5626,8 +5678,6 @@ ENGINE_ERROR_CODE EventuallyPersistentEngine::deleteWithMeta(
     }
 
     uint8_t opcode = request->message.header.request.opcode;
-    const char *key_ptr = reinterpret_cast<const char*>(request->bytes);
-    key_ptr += sizeof(request->bytes);
     uint16_t vbucket = ntohs(request->message.header.request.vbucket);
     uint64_t cas = ntohll(request->message.header.request.cas);
 
@@ -5636,41 +5686,46 @@ ENGINE_ERROR_CODE EventuallyPersistentEngine::deleteWithMeta(
     uint64_t seqno = ntohll(request->message.body.seqno);
     uint64_t metacas = ntohll(request->message.body.cas);
 
-    bool force = false;
-    ExtendedMetaData *emd = NULL;
+    bool skipConflictResolution = false;
+    GenerateCas generateCas = GenerateCas::No;
+    std::unique_ptr<ExtendedMetaData> emd;
+    int keyOffset = 0;
+    protocol_binary_response_status error = PROTOCOL_BINARY_RESPONSE_SUCCESS;
+    if ((error = decodeWithMetaOptions(request, generateCas,
+                                      skipConflictResolution, keyOffset)) !=
+        PROTOCOL_BINARY_RESPONSE_SUCCESS) {
+        return sendResponse(response, NULL, 0, NULL, 0, NULL, 0,
+                            PROTOCOL_BINARY_RAW_BYTES, error, 0, cookie);
+    }
 
-    if (extlen == 28) {
-        uint32_t options;
-        memcpy(&options, request->bytes + sizeof(request->bytes),
-               sizeof(options));
-        key_ptr += 4;       // 4 bytes for options
-        if (ntohl(options) & SKIP_CONFLICT_RESOLUTION_FLAG) {
-            force = true;
-        }
-    } else if (extlen == 26) {
-        uint16_t nmeta;
+    if (extlen == 26 || extlen == 30) {
+        uint16_t nmeta = 0;
         memcpy(&nmeta, request->bytes + sizeof(request->bytes),
                sizeof(nmeta));
-        key_ptr += 2;       // 2 bytes for nmeta
+        keyOffset += 2; // 2 bytes for nmeta
         nmeta = ntohs(nmeta);
         if (nmeta > 0) {
-            emd = new ExtendedMetaData(key_ptr + nkey, nmeta);
-
-            if (emd == NULL ) {
+            try {
+                emd.reset(new ExtendedMetaData(request->bytes +
+                                               nkey +
+                                               keyOffset, nmeta));
+            } catch (const std::bad_alloc&) {
                 return sendResponse(response, NULL, 0, NULL, 0, NULL, 0,
-                                    PROTOCOL_BINARY_RAW_BYTES,
-                                    PROTOCOL_BINARY_RESPONSE_ENOMEM, 0, cookie);
+                            PROTOCOL_BINARY_RAW_BYTES,
+                            PROTOCOL_BINARY_RESPONSE_ENOMEM, 0, cookie);
             }
 
             if (emd->getStatus() == ENGINE_EINVAL) {
-                delete emd;
                 return sendResponse(response, NULL, 0, NULL, 0, NULL, 0,
-                                    PROTOCOL_BINARY_RAW_BYTES,
-                                    PROTOCOL_BINARY_RESPONSE_EINVAL, 0, cookie);
+                            PROTOCOL_BINARY_RAW_BYTES,
+                            PROTOCOL_BINARY_RESPONSE_EINVAL, 0, cookie);
             }
         }
     }
 
+    const char *key_ptr = reinterpret_cast<const char*>(request->bytes +
+                                                        keyOffset +
+                                                        sizeof(request->bytes));
     std::string key(key_ptr, nkey);
 
     ItemMetaData itm_meta(metacas, seqno, flags, expiration);
@@ -5679,10 +5734,13 @@ ENGINE_ERROR_CODE EventuallyPersistentEngine::deleteWithMeta(
     uint64_t by_seqno = 0;
     uint64_t vb_uuid = 0;
 
-    ENGINE_ERROR_CODE ret = epstore->deleteWithMeta(key, &cas, &by_seqno, vbucket, cookie,
-                                                    force, &itm_meta, false, true, 0, emd);
-
-    delete emd;
+    ENGINE_ERROR_CODE ret = epstore->deleteWithMeta(key, &cas, &by_seqno,
+                                                    vbucket, cookie,
+                                                    skipConflictResolution,
+                                                    &itm_meta, false,
+                                                    GenerateBySeqno::Yes,
+                                                    generateCas, 0,
+                                                    emd.get(), false);
 
     if (ret == ENGINE_SUCCESS) {
         stats.numOpsDelMeta++;
index d16b735..a0cd04a 100644 (file)
@@ -1007,6 +1007,23 @@ protected:
     // server.
     void initializeEngineCallbacks();
 
+    /*
+     * Private helper method for decoding the options on set/del_with_meta.
+     * Tighly coupled to the logic of both those functions, it will
+     * take a request pointer and locate and validate any options within.
+     * @param request pointer to the set/del_with_meta request packet
+     * @param generateCas set to Yes if CAS regeneration is enabled.
+     * @param skipConflictResolution set to true if conflict resolution should
+     *        not be performed.
+     * @param keyOffset set to the number of bytes which are to be skipped to
+     *        locate the key.
+     */
+    protocol_binary_response_status decodeWithMetaOptions(
+                              protocol_binary_request_delete_with_meta* request,
+                              GenerateCas& generateCas,
+                              bool& skipConflictResolution,
+                              int& keyOffset);
+
     SERVER_HANDLE_V1 *serverApi;
     EventuallyPersistentStore *epstore;
     WorkLoadPolicy *workload;
index 65c3c41..412371c 100644 (file)
@@ -2202,7 +2202,9 @@ ENGINE_ERROR_CODE TapConsumer::mutation(uint32_t opaque, const void* key,
         ret = epstore->addTAPBackfillItem(*item, nru, true);
     }
     else {
-        ret = epstore->setWithMeta(*item, 0, NULL, this, true, true, nru, true,
+        ret = epstore->setWithMeta(*item, 0, NULL, this, true, true, nru,
+                                   GenerateBySeqno::Yes,
+                                   GenerateCas::No,
                                    NULL, true);
     }
 
@@ -2257,7 +2259,8 @@ ENGINE_ERROR_CODE TapConsumer::deletion(uint32_t opaque, const void* key,
     ItemMetaData itemMeta(cas, revSeqno, 0, 0);
     ret = epstore->deleteWithMeta(key_str, &delCas, NULL, vbucket, this, true,
                                   &itemMeta, isBackfillPhase(vbucket),
-                                  true, 0, NULL, true);
+                                  GenerateBySeqno::Yes, GenerateCas::No, 0,
+                                  NULL, true);
 
     if (ret == ENGINE_KEY_ENOENT) {
         ret = ENGINE_SUCCESS;
index e3cc8ce..bbca8f4 100644 (file)
@@ -346,50 +346,6 @@ protocol_binary_request_header* createPacket(uint8_t opcode,
     return req;
 }
 
-void add_with_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *key,
-                   const size_t keylen, const char *val, const size_t vallen,
-                   const uint32_t vb, ItemMetaData *itemMeta,
-                   bool skipConflictResolution, uint8_t datatype,
-                   bool includeExtMeta) {
-    int blen = 0;
-    char *ext;
-    ExtendedMetaData *emd = NULL;
-    if (!includeExtMeta) {
-        blen = skipConflictResolution ? 28 : 24;
-        ext = new char[blen];
-        encodeWithMetaExt(ext, itemMeta);
-
-        if (skipConflictResolution) {
-            uint32_t flag = SKIP_CONFLICT_RESOLUTION_FLAG;
-            flag = htonl(flag);
-            memcpy(ext + 24, (char*)&flag, sizeof(flag));
-        }
-    } else {
-        blen = 26;
-        ext = new char[blen];
-        encodeWithMetaExt(ext, itemMeta);
-        emd = new ExtendedMetaData();
-        // nmeta added to ext below
-    }
-
-    protocol_binary_request_header *pkt;
-    if (emd) {
-        std::pair<const char*, uint16_t> meta = emd->getExtMeta();
-        uint16_t nmeta = htons(meta.second);
-        memcpy(ext + 24, (char*)&nmeta, sizeof(nmeta));
-        pkt = createPacket(PROTOCOL_BINARY_CMD_ADD_WITH_META, vb, 0, ext, blen, key,
-                           keylen, val, vallen, datatype, meta.first, meta.second);
-        delete emd;
-    } else {
-        pkt = createPacket(PROTOCOL_BINARY_CMD_ADD_WITH_META, vb, 0, ext, blen, key,
-                           keylen, val, vallen, datatype);
-    }
-    check(h1->unknown_command(h, NULL, pkt, add_response) == ENGINE_SUCCESS,
-          "Expected to be able to store with meta");
-    cb_free(pkt);
-    delete[] ext;
-}
-
 void changeVBFilter(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, std::string name,
                     std::map<uint16_t, uint64_t> &filtermap) {
     std::stringstream value;
@@ -428,46 +384,34 @@ ENGINE_ERROR_CODE del(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *key,
 void del_with_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *key,
                    const size_t keylen, const uint32_t vb,
                    ItemMetaData *itemMeta, uint64_t cas_for_delete,
-                   bool skipConflictResolution, bool includeExtMeta,
-                   const void *cookie) {
-    int blen = 0;
-    char *ext;
-    ExtendedMetaData *emd = NULL;
-    if (!includeExtMeta) {
-        blen = skipConflictResolution ? 28 : 24;
-        ext = new char[blen];
-        encodeWithMetaExt(ext, itemMeta);
-
-        if (skipConflictResolution) {
-            uint32_t flag = SKIP_CONFLICT_RESOLUTION_FLAG;
-            flag = htonl(flag);
-            memcpy(ext + 24, (char*)&flag, sizeof(flag));
-        }
-    } else {
-        blen = 26;
-        ext = new char[blen];
-        encodeWithMetaExt(ext, itemMeta);
-        emd = new ExtendedMetaData();
-        // nmeta added to ext below
+                   uint32_t options, const void *cookie,
+                   const std::vector<char>& nmeta) {
+    int blen = 24;
+    std::unique_ptr<char[]> ext(new char[30]);
+    std::unique_ptr<ExtendedMetaData> emd;
+
+    encodeWithMetaExt(ext.get(), itemMeta);
+
+    if (options) {
+        uint32_t optionsSwapped = htonl(options);
+        memcpy(ext.get() + blen, (char*)&optionsSwapped, sizeof(optionsSwapped));
+        blen += sizeof(uint32_t);
     }
 
-    protocol_binary_request_header *pkt;
-    if (emd) {
-        std::pair<const char*, uint16_t> meta = emd->getExtMeta();
-        uint16_t nmeta = htons(meta.second);
-        memcpy(ext + 24, (char*)&nmeta, sizeof(nmeta));
-        pkt = createPacket(PROTOCOL_BINARY_CMD_DEL_WITH_META, vb, cas_for_delete,
-                           ext, blen, key, keylen, NULL, 0, 0x00, meta.first,
-                           meta.second);
-        delete emd;
-    } else {
-        pkt = createPacket(PROTOCOL_BINARY_CMD_DEL_WITH_META, vb, cas_for_delete,
-                           ext, blen, key, keylen, NULL, 0, 0x00);
+    if (nmeta.size() > 0) {
+        uint16_t nmetaSize = htons(nmeta.size());
+        memcpy(ext.get() + blen, (char*)&nmetaSize, sizeof(nmetaSize));
+        blen += sizeof(uint16_t);
     }
+
+    protocol_binary_request_header *pkt;
+    pkt = createPacket(PROTOCOL_BINARY_CMD_DEL_WITH_META, vb, cas_for_delete,
+                       ext.get(), blen, key, keylen, NULL, 0,
+                       PROTOCOL_BINARY_RAW_BYTES, nmeta.data(), nmeta.size());
+
     check(h1->unknown_command(h, cookie, pkt, add_response_set_del_meta) == ENGINE_SUCCESS,
           "Expected to be able to delete with meta");
     cb_free(pkt);
-    delete[] ext;
 }
 
 void evict_key(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *key,
@@ -773,50 +717,58 @@ void verify_all_vb_seqnos(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
     }
 }
 
-void set_with_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *key,
-                   const size_t keylen, const char *val, const size_t vallen,
-                   const uint32_t vb, ItemMetaData *itemMeta,
-                   uint64_t cas_for_set, bool skipConflictResolution,
-                   uint8_t datatype, bool includeExtMeta, const void *cookie) {
-    int blen = 0;
-    char *ext;
-    ExtendedMetaData *emd = NULL;
-    if (!includeExtMeta) {
-        blen = skipConflictResolution ? 28 : 24;
-        ext = new char[blen];
-        encodeWithMetaExt(ext, itemMeta);
-
-        if (skipConflictResolution) {
-            uint32_t flag = SKIP_CONFLICT_RESOLUTION_FLAG;
-            flag = htonl(flag);
-            memcpy(ext + 24, (char*)&flag, sizeof(flag));
-        }
-    } else {
-        blen = 26;
-        ext = new char[blen];
-        encodeWithMetaExt(ext, itemMeta);
-        emd = new ExtendedMetaData();
-        // nmeta added to ext below
+static void store_with_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
+                     protocol_binary_command cmd, const char *key,
+                     const size_t keylen, const char *val, const size_t vallen,
+                     const uint32_t vb, ItemMetaData *itemMeta,
+                     uint64_t cas_for_store, uint32_t options,
+                     uint8_t datatype, const void *cookie,
+                     const std::vector<char>& nmeta) {
+    int blen = 24;
+    std::unique_ptr<char[]> ext(new char[30]);
+    std::unique_ptr<ExtendedMetaData> emd;
+
+    encodeWithMetaExt(ext.get(), itemMeta);
+
+    if (options) {
+        uint32_t optionsSwapped = htonl(options);
+        memcpy(ext.get() + blen, (char*)&optionsSwapped, sizeof(optionsSwapped));
+        blen += sizeof(uint32_t);
     }
 
-    protocol_binary_request_header *pkt;
-    if (emd) {
-        std::pair<const char*, uint16_t> meta = emd->getExtMeta();
-        uint16_t nmeta = htons(meta.second);
-        memcpy(ext + 24, (char*)&nmeta, sizeof(nmeta));
-        pkt = createPacket(PROTOCOL_BINARY_CMD_SET_WITH_META, vb, cas_for_set, ext,
-                           blen, key, keylen, val, vallen, datatype, meta.first,
-                           meta.second);
-        delete emd;
-    } else {
-        pkt = createPacket(PROTOCOL_BINARY_CMD_SET_WITH_META, vb, cas_for_set, ext,
-                           blen, key, keylen, val, vallen, datatype);
+    if (nmeta.size() > 0) {
+        uint16_t nmetaSize = htons(nmeta.size());
+        memcpy(ext.get() + blen, (char*)&nmetaSize, sizeof(nmetaSize));
+        blen += sizeof(uint16_t);
     }
 
+    protocol_binary_request_header *pkt;
+    pkt = createPacket(cmd, vb, cas_for_store, ext.get(), blen, key, keylen,
+                       val, vallen, datatype, nmeta.data(), nmeta.size());
+
     check(h1->unknown_command(h, cookie, pkt, add_response_set_del_meta) == ENGINE_SUCCESS,
           "Expected to be able to store with meta");
     cb_free(pkt);
-    delete[] ext;
+}
+
+void set_with_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *key,
+                   const size_t keylen, const char *val, const size_t vallen,
+                   const uint32_t vb, ItemMetaData *itemMeta,
+                   uint64_t cas_for_set, uint32_t options, uint8_t datatype,
+                   const void *cookie, const std::vector<char>& nmeta) {
+    store_with_meta(h, h1, PROTOCOL_BINARY_CMD_SET_WITH_META, key, keylen, val,
+                    vallen, vb, itemMeta, cas_for_set, options, datatype,
+                    cookie, nmeta);
+}
+
+void add_with_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *key,
+                   const size_t keylen, const char *val, const size_t vallen,
+                   const uint32_t vb, ItemMetaData *itemMeta,
+                   uint64_t cas_for_add, uint32_t options, uint8_t datatype,
+                   const void *cookie, const std::vector<char>& nmeta) {
+    store_with_meta(h, h1, PROTOCOL_BINARY_CMD_ADD_WITH_META, key, keylen, val,
+                    vallen, vb, itemMeta, cas_for_add, options, datatype,
+                    cookie, nmeta);
 }
 
 void return_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *key,
index c728a2c..6c5e2c0 100644 (file)
@@ -359,42 +359,56 @@ void compact_db(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
 // XDCR Operations
 void set_drift_counter_state(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
                              int64_t initialDrift);
-void add_with_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *key,
-                   const size_t keylen, const char *val, const size_t vallen,
-                   const uint32_t vb, ItemMetaData *itemMeta,
-                   bool skipConflictResolution = false,
-                   uint8_t datatype = 0x00, bool includeExtMeta = false);
+
 bool get_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char* key,
               bool reqExtMeta = false, const void* cookie = nullptr);
-void del_with_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *key,
-                   const size_t keylen, const uint32_t vb,
-                   ItemMetaData *itemMeta, uint64_t cas_for_delete = 0,
-                   bool skipConflictResolution = false,
-                   bool includeExtMeta = false, const void *cookie = NULL);
+
 void set_with_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *key,
                    const size_t keylen, const char *val, const size_t vallen,
                    const uint32_t vb, ItemMetaData *itemMeta,
-                   uint64_t cas_for_set, bool skipConflictResolution = false,
-                   uint8_t datatype = 0x00, bool includeExtMeta = false,
-                   const void *cookie = NULL);
+                   uint64_t cas_for_set, uint32_t options = 0,
+                   uint8_t datatype = PROTOCOL_BINARY_RAW_BYTES,
+                   const void* cookie = nullptr,
+                   const std::vector<char>& nmeta = {});
+
+void add_with_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *key,
+                   const size_t keylen, const char *val, const size_t vallen,
+                   const uint32_t vb, ItemMetaData *itemMeta,
+                   uint64_t cas_for_add = 0, uint32_t options = 0,
+                   uint8_t datatype = PROTOCOL_BINARY_RAW_BYTES,
+                   const void* cookie = nullptr,
+                   const std::vector<char>& nmeta = {});
+
+void del_with_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *key,
+                   const size_t keylen, const uint32_t vb,
+                   ItemMetaData* itemMeta, uint64_t cas_for_delete = 0,
+                   uint32_t options = 0,  const void *cookie = nullptr,
+                   const std::vector<char>& nmeta = {});
+
 void return_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *key,
                  const size_t keylen, const char *val, const size_t vallen,
                  const uint32_t vb, const uint64_t cas, const uint32_t flags,
                  const uint32_t exp, const uint32_t type,
-                 uint8_t datatype = 0x00, const void *cookie = NULL);
+                 uint8_t datatype = PROTOCOL_BINARY_RAW_BYTES,
+                 const void *cookie = nullptr);
+
 void set_ret_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *key,
                   const size_t keylen, const char *val, const size_t vallen,
                   const uint32_t vb, const uint64_t cas = 0,
                   const uint32_t flags = 0, const uint32_t exp = 0,
-                  uint8_t datatype = 0x00, const void *cookie = NULL);
+                  uint8_t datatype = PROTOCOL_BINARY_RAW_BYTES,
+                  const void *cookie = nullptr);
+
 void add_ret_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *key,
                   const size_t keylen, const char *val, const size_t vallen,
                   const uint32_t vb, const uint64_t cas = 0,
                   const uint32_t flags = 0, const uint32_t exp = 0,
-                  uint8_t datatype = 0x00, const void *cookie = NULL);
+                  uint8_t datatype = PROTOCOL_BINARY_RAW_BYTES,
+                  const void *cookie = nullptr);
+
 void del_ret_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *key,
                   const size_t keylen, const uint32_t vb,
-                  const uint64_t cas = 0, const void *cookie = NULL);
+                  const uint64_t cas = 0, const void *cookie = nullptr);
 
 void set_degraded_mode(ENGINE_HANDLE *h,
                        ENGINE_HANDLE_V1 *h1,
index 5a35a99..30c49a1 100644 (file)
@@ -2582,7 +2582,7 @@ static enum test_result test_datatype(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) {
     itm_meta.exptime = info.exptime;
     itm_meta.flags = info.flags;
     set_with_meta(h, h1, key1, strlen(key1), val1, strlen(val1), 0, &itm_meta,
-                  last_cas, false, info.datatype, false, cookie);
+                  last_cas, 0, info.datatype, cookie);
 
     checkeq(ENGINE_SUCCESS,
             h1->get(h, cookie, &itm, key1, strlen(key1), 0),
@@ -2614,7 +2614,7 @@ static enum test_result test_datatype_with_unknown_command(ENGINE_HANDLE *h,
 
     //SET_WITH_META
     set_with_meta(h, h1, key, strlen(key), val, strlen(val), 0, &itm_meta,
-                  0, false, datatype, false, cookie);
+                  0, 0, datatype, cookie);
 
     checkeq(ENGINE_SUCCESS,
             h1->get(h, cookie, &itm, key, strlen(key), 0),
index 7e67169..e223691 100644 (file)
@@ -403,7 +403,7 @@ static enum test_result test_delete_with_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1
     const void *cookie = testHarness.create_cookie();
 
     // delete an item with meta data
-    del_with_meta(h, h1, key1, keylen, 0, &itemMeta, 0, false, false, cookie);
+    del_with_meta(h, h1, key1, keylen, 0, &itemMeta, 0/*cas*/, 0/*options*/, cookie);
 
     check(last_uuid == vb_uuid, "Expected valid vbucket uuid");
     check(last_seqno == high_seqno + 1, "Expected valid sequence number");
@@ -415,7 +415,7 @@ static enum test_result test_delete_with_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1
     testHarness.set_mutation_extras_handling(cookie, false);
 
     // delete an item with meta data
-    del_with_meta(h, h1, key2, keylen, 0, &itemMeta, 0, false, false, cookie);
+    del_with_meta(h, h1, key2, keylen, 0, &itemMeta, 0/*cas*/, 0/*options*/, cookie);
 
     check(last_uuid == vb_uuid, "Expected same vbucket uuid");
     check(last_seqno == high_seqno + 1, "Expected same sequence number");
@@ -823,7 +823,7 @@ static enum test_result test_set_with_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h
 
     // do set with meta with the correct cas value. should pass.
     set_with_meta(h, h1, key, keylen, newVal, newValLen, 0, &itm_meta, cas_for_set,
-                  false, 0, false, cookie);
+                  0, 0, cookie);
     checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS, last_status.load(), "Expected success");
     check(last_uuid == vb_uuid, "Expected valid vbucket uuid");
     check(last_seqno == high_seqno + 1, "Expected valid sequence number");
@@ -845,7 +845,7 @@ static enum test_result test_set_with_meta(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h
     itm_meta.revSeqno++;
     cas_for_set = last_meta.cas;
     set_with_meta(h, h1, key, keylen, newVal, newValLen, 0, &itm_meta, cas_for_set,
-                  false, 0, false, cookie);
+                  false, 0, cookie);
     checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS, last_status.load(), "Expected success");
     check(last_uuid == vb_uuid, "Expected same vbucket uuid");
     check(last_seqno == high_seqno + 1, "Expected same sequence number");
@@ -1411,33 +1411,39 @@ static enum test_result test_set_meta_lww_conflict_resolution(ENGINE_HANDLE *h,
     checkeq(0, get_int_stat(h, h1, "ep_num_ops_set_meta"),
           "Expect zero setMeta ops");
 
-    set_with_meta(h, h1, "key", 3, NULL, 0, 0, &itemMeta, 0, false,
-                  PROTOCOL_BINARY_RAW_BYTES, false);
+    set_with_meta(h, h1, "key", 3, NULL, 0, 0, &itemMeta, 0,
+                  FORCE_ACCEPT_WITH_META_OPS);
     checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS, last_status.load(), "Expected success");
     checkeq(0, get_int_stat(h, h1, "ep_bg_meta_fetched"),
             "Expected no bg meta fetchs, thanks to bloom filters");
 
     // Check all meta data is the same
-    set_with_meta(h, h1, "key", 3, NULL, 0, 0, &itemMeta, 0, false,
-                  PROTOCOL_BINARY_RAW_BYTES, false);
+    set_with_meta(h, h1, "key", 3, NULL, 0, 0, &itemMeta, 0,
+                  FORCE_ACCEPT_WITH_META_OPS);
     checkeq(PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS, last_status.load(), "Expected exists");
     checkeq(1, get_int_stat(h, h1, "ep_num_ops_set_meta_res_fail"),
           "Expected set meta conflict resolution failure");
 
     // Check that an older cas fails
     itemMeta.cas = 0xdeadbeee;
-    set_with_meta(h, h1, "key", 3, NULL, 0, 0, &itemMeta, 0, false,
-                  PROTOCOL_BINARY_RAW_BYTES, false);
+    set_with_meta(h, h1, "key", 3, NULL, 0, 0, &itemMeta, 0,
+                  FORCE_ACCEPT_WITH_META_OPS);
     checkeq(PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS, last_status.load(), "Expected exists");
     checkeq(2, get_int_stat(h, h1, "ep_num_ops_set_meta_res_fail"),
           "Expected set meta conflict resolution failure");
 
     // Check that a higher cas passes
     itemMeta.cas = 0xdeadbeff;
-    set_with_meta(h, h1, "key", 3, NULL, 0, 0, &itemMeta, 0, false,
-                  PROTOCOL_BINARY_RAW_BYTES, false);
+    set_with_meta(h, h1, "key", 3, NULL, 0, 0, &itemMeta, 0,
+                  FORCE_ACCEPT_WITH_META_OPS);
     checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS, last_status.load(), "Expected success");
 
+    // Check that we fail requests if the force flag is not set
+    itemMeta.cas = 0xdeadbeff + 1;
+    set_with_meta(h, h1, "key", 3, NULL, 0, 0, &itemMeta, 0,
+                  0/*options*/);
+    checkeq(PROTOCOL_BINARY_RESPONSE_EINVAL, last_status.load(), "Expected EINVAL");
+
     return SUCCESS;
 }
 
@@ -1521,13 +1527,17 @@ static enum test_result test_del_meta_lww_conflict_resolution(ENGINE_HANDLE *h,
     itemMeta.exptime = 0;
     itemMeta.flags = 0xdeadbeef;
 
-    del_with_meta(h, h1, "key", 3, 0, &itemMeta, 0, false, true);
+    // first check the command fails if no force is set
+    del_with_meta(h, h1, "key", 3, 0, &itemMeta, 0, 0/*options*/);
+    checkeq(PROTOCOL_BINARY_RESPONSE_EINVAL, last_status.load(), "Expected EINVAL");
+
+    del_with_meta(h, h1, "key", 3, 0, &itemMeta, 0, FORCE_ACCEPT_WITH_META_OPS);
     checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS, last_status.load(), "Expected success");
     wait_for_flusher_to_settle(h, h1);
     wait_for_stat_to_be(h, h1, "curr_items", 0);
 
     // Check all meta data is the same
-    del_with_meta(h, h1, "key", 3, 0, &itemMeta, 0, false, true);
+    del_with_meta(h, h1, "key", 3, 0, &itemMeta, 0, FORCE_ACCEPT_WITH_META_OPS);
     checkeq(PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS, last_status.load(), "Expected exists");
     checkeq(1, get_int_stat(h, h1, "ep_num_ops_del_meta_res_fail"),
           "Expected delete meta conflict resolution failure");
@@ -1535,7 +1545,7 @@ static enum test_result test_del_meta_lww_conflict_resolution(ENGINE_HANDLE *h,
     // Check that higher rev seqno but lower cas fails
     itemMeta.cas = info.cas;
     itemMeta.revSeqno = 11;
-    del_with_meta(h, h1, "key", 3, 0, &itemMeta, 0, false, true);
+    del_with_meta(h, h1, "key", 3, 0, &itemMeta, 0, FORCE_ACCEPT_WITH_META_OPS);
     checkeq(PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS, last_status.load(), "Expected exists");
     checkeq(2, get_int_stat(h, h1, "ep_num_ops_del_meta_res_fail"),
           "Expected delete meta conflict resolution failure");
@@ -1543,7 +1553,7 @@ static enum test_result test_del_meta_lww_conflict_resolution(ENGINE_HANDLE *h,
     // Check that a higher cas and lower rev seqno passes
     itemMeta.cas = info.cas + 2;
     itemMeta.revSeqno = 9;
-    del_with_meta(h, h1, "key", 3, 0, &itemMeta, 0, false, true);
+    del_with_meta(h, h1, "key", 3, 0, &itemMeta, 0, FORCE_ACCEPT_WITH_META_OPS);
     checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS, last_status.load(), "Expected sucess");
 
     return SUCCESS;
@@ -1611,7 +1621,7 @@ static enum test_result test_set_with_meta_and_check_drift_stats(ENGINE_HANDLE *
                 itm_meta.cas = 1;
             }
             set_with_meta(h, h1, key.data(), key.size(), NULL, 0, ii, &itm_meta,
-                          0, false, PROTOCOL_BINARY_RAW_BYTES, true, 0);
+                          0, FORCE_ACCEPT_WITH_META_OPS);
             checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS, last_status.load(),
                     "Expected success");
         }
@@ -1692,7 +1702,7 @@ static enum test_result test_del_with_meta_and_check_drift_stats(ENGINE_HANDLE *
             ItemMetaData itm_meta;
             itm_meta.cas = 1; // set to 1
             set_with_meta(h, h1, key.data(), key.size(), NULL, 0, ii, &itm_meta,
-                          0, false, PROTOCOL_BINARY_RAW_BYTES, true, 0);
+                          0, FORCE_ACCEPT_WITH_META_OPS);
             checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS, last_status.load(),
                     "Expected success");
         }
@@ -1720,7 +1730,7 @@ static enum test_result test_del_with_meta_and_check_drift_stats(ENGINE_HANDLE *
                 itm_meta.cas = 2;
             }
             del_with_meta(h, h1, key.data(), key.size(), ii, &itm_meta,
-                          1, false, PROTOCOL_BINARY_RAW_BYTES);
+                          1, FORCE_ACCEPT_WITH_META_OPS);
             checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS, last_status.load(),
                     "Expected success");
         }
@@ -1797,6 +1807,226 @@ static enum test_result test_setting_drift_threshold(ENGINE_HANDLE *h, ENGINE_HA
     return SUCCESS;
 }
 
+/*
+ * Perform set_with_meta and check CAS regeneration is ok.
+ */
+static enum test_result test_cas_regeneration(ENGINE_HANDLE *h,
+                                              ENGINE_HANDLE_V1 *h1) {
+
+    // First store a key from the past (small CAS).
+    ItemMetaData itemMeta;
+    itemMeta.revSeqno = 10;
+    itemMeta.cas = 0x1;
+    itemMeta.exptime = 0;
+    itemMeta.flags = 0xdeadbeef;
+    int force = 0;
+
+    if (strstr(testHarness.get_current_testcase()->cfg,
+               "conflict_resolution_type=lww") != nullptr) {
+        force = FORCE_ACCEPT_WITH_META_OPS;
+    }
+
+    // Set the key with a low CAS value
+    set_with_meta(h, h1, "key", 3, nullptr, 0, 0, &itemMeta, 0, force);
+    checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS, last_status.load(), "Expected success");
+
+    check(get_meta(h, h1, "key"), "Failed to get_meta");
+
+    // CAS must be what we set.
+    checkeq(itemMeta.cas, last_meta.cas, "CAS is not the value we stored");
+
+    itemMeta.cas++;
+
+    // Check that the code requires skip
+    set_with_meta(h, h1, "key", 3, nullptr, 0, 0, &itemMeta, 0,
+                  REGENERATE_CAS/*but no skip*/);
+    checkeq(PROTOCOL_BINARY_RESPONSE_EINVAL, last_status.load(),
+            "Expected EINVAL");
+
+    set_with_meta(h, h1, "key", 3, nullptr, 0, 0, &itemMeta, 0,
+                  REGENERATE_CAS|SKIP_CONFLICT_RESOLUTION_FLAG);
+
+    checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS, last_status.load(),
+            "Expected success");
+
+    check(get_meta(h, h1, "key"), "Failed to get_meta");
+
+    uint64_t cas = last_meta.cas;
+    // Check item has a new CAS
+    checkne(itemMeta.cas, cas, "CAS was not regenerated");
+
+    itemMeta.cas++;
+    // All flags set should still regen the cas (lww and seqno)
+    set_with_meta(h, h1, "key", 3, nullptr, 0, 0, &itemMeta, 0,
+                  REGENERATE_CAS|SKIP_CONFLICT_RESOLUTION_FLAG|force);
+
+    checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS, last_status.load(),
+            "Expected success");
+
+    check(get_meta(h, h1, "key"), "Failed to get_meta");
+    // Check item has a new CAS
+    checkne(itemMeta.cas, last_meta.cas, "CAS was not regenerated");
+    checkne(cas, last_meta.cas, "CAS was not regenerated");
+    return SUCCESS;
+}
+
+/*
+ * Test that we can send options and nmeta
+ * The nmeta is just going to be ignored though, but should not fail
+ */
+static enum test_result test_cas_options_and_nmeta(ENGINE_HANDLE *h,
+                                                   ENGINE_HANDLE_V1 *h1) {
+    ItemMetaData itemMeta;
+    itemMeta.revSeqno = 10;
+    itemMeta.cas = 0x1;
+    itemMeta.exptime = 0;
+    itemMeta.flags = 0xdeadbeef;
+
+    // Watson (4.6) accepts valid encodings, but ignores them
+    std::vector<char> junkMeta = {-2,-1,2,3};
+
+    // Set the key and junk nmeta
+    set_with_meta(h, h1, "key", 3, NULL, 0, 0, &itemMeta, 0,
+                  FORCE_ACCEPT_WITH_META_OPS, PROTOCOL_BINARY_RAW_BYTES,
+                  nullptr, junkMeta);
+    checkeq(PROTOCOL_BINARY_RESPONSE_EINVAL, last_status.load(), "Expected EINVAL");
+
+    // Set the key and junk nmeta that's quite large
+    junkMeta.resize(std::numeric_limits<uint16_t>::max());
+    set_with_meta(h, h1, "key", 3, NULL, 0, 0, &itemMeta, 0,
+                  FORCE_ACCEPT_WITH_META_OPS, PROTOCOL_BINARY_RAW_BYTES,
+                  nullptr, junkMeta);
+    checkeq(PROTOCOL_BINARY_RESPONSE_EINVAL, last_status.load(), "Expected EINVAL");
+
+    // Test that valid meta can be sent. It should be ignored and success
+    // returned
+    // Encodings which should not fail, see ext_meta_parser.cc
+#pragma pack(1)
+    struct adjusted_time_metadata {
+        uint8_t type;
+        uint16_t length;
+        int64_t value;
+    };
+    struct conf_res_metadata {
+        uint8_t type;
+        uint16_t length;
+        uint8_t value;
+    };
+    struct with_cas_metadata1 {
+        uint8_t version;
+        adjusted_time_metadata adjusted_time;
+    };
+    struct with_cas_metadata2 {
+        uint8_t version;
+        conf_res_metadata conf_res;
+    };
+    struct with_cas_metadata3 {
+        uint8_t version;
+        conf_res_metadata conf_res;
+        adjusted_time_metadata adjusted_time;
+    };
+    struct with_cas_metadata4 {
+        uint8_t version;
+        adjusted_time_metadata adjusted_time;
+        conf_res_metadata conf_res;
+    };
+#pragma pack()
+
+    {
+        with_cas_metadata1 validMetaData = {META_EXT_VERSION_ONE,
+                                            {CMD_META_ADJUSTED_TIME,
+                                             htons(sizeof(int64_t)), -1}};
+        std::vector<char> validMetaVector(reinterpret_cast<char*>(&validMetaData),
+                                          reinterpret_cast<char*>(&validMetaData) +
+                                          sizeof(validMetaData));
+
+        // Set the key with a low CAS value and real nmeta
+        set_with_meta(h, h1, "key1", 4, nullptr, 0, 0, &itemMeta, 0,
+                      FORCE_ACCEPT_WITH_META_OPS, PROTOCOL_BINARY_RAW_BYTES,
+                      nullptr, validMetaVector);
+        checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS, last_status.load(),
+                "Expected success");
+
+        itemMeta.cas++;
+        del_with_meta(h, h1, "key1", 4, 0, &itemMeta, 0,
+                      FORCE_ACCEPT_WITH_META_OPS, nullptr, validMetaVector);
+        checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS, last_status.load(),
+                "Expected success");
+    }
+
+    {
+        with_cas_metadata2 validMetaData = {META_EXT_VERSION_ONE,
+                                            {CMD_META_CONFLICT_RES_MODE,
+                                             htons(sizeof(uint8_t)), 0xff}};
+        std::vector<char> validMetaVector(reinterpret_cast<char*>(&validMetaData),
+                                          reinterpret_cast<char*>(&validMetaData) +
+                                          sizeof(validMetaData));
+
+        // Set the key with a low CAS value and real nmeta
+        set_with_meta(h, h1, "key2", 4, nullptr, 0, 0, &itemMeta, 0,
+                      FORCE_ACCEPT_WITH_META_OPS, PROTOCOL_BINARY_RAW_BYTES,
+                      nullptr, validMetaVector);
+        checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS, last_status.load(),
+                "Expected success");
+
+        itemMeta.cas++;
+        del_with_meta(h, h1, "key2", 4, 0, &itemMeta, 0,
+                      FORCE_ACCEPT_WITH_META_OPS, nullptr, validMetaVector);
+        checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS, last_status.load(),
+                "Expected success");
+    }
+
+    {
+        with_cas_metadata3 validMetaData = {META_EXT_VERSION_ONE,
+                                            {CMD_META_CONFLICT_RES_MODE,
+                                             htons(sizeof(uint8_t)), 0xff},
+                                            {CMD_META_ADJUSTED_TIME,
+                                             htons(sizeof(int64_t)), -1}};
+        std::vector<char> validMetaVector(reinterpret_cast<char*>(&validMetaData),
+                                          reinterpret_cast<char*>(&validMetaData) +
+                                          sizeof(validMetaData));
+
+        // Set the key with a low CAS value and real nmeta
+        set_with_meta(h, h1, "key3", 4, nullptr, 0, 0, &itemMeta, 0,
+                      FORCE_ACCEPT_WITH_META_OPS, PROTOCOL_BINARY_RAW_BYTES,
+                      nullptr, validMetaVector);
+        checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS, last_status.load(),
+                "Expected success");
+
+        itemMeta.cas++;
+        del_with_meta(h, h1, "key3", 4, 0, &itemMeta, 0,
+                      FORCE_ACCEPT_WITH_META_OPS, nullptr, validMetaVector);
+        checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS, last_status.load(),
+                "Expected success");
+    }
+
+    {
+        with_cas_metadata4 validMetaData = {META_EXT_VERSION_ONE,
+                                            {CMD_META_ADJUSTED_TIME,
+                                             htons(sizeof(int64_t)), -1},
+                                            {CMD_META_CONFLICT_RES_MODE,
+                                             htons(sizeof(uint8_t)), 0xff}};
+        std::vector<char> validMetaVector(reinterpret_cast<char*>(&validMetaData),
+                                          reinterpret_cast<char*>(&validMetaData) +
+                                          sizeof(validMetaData));
+
+        // Set the key with a low CAS value and real nmeta
+        set_with_meta(h, h1, "key4", 4, NULL, 0, 0, &itemMeta, 0,
+                      FORCE_ACCEPT_WITH_META_OPS, PROTOCOL_BINARY_RAW_BYTES,
+                      nullptr, validMetaVector);
+        checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS, last_status.load(),
+                "Expected success");
+
+        itemMeta.cas++;
+        del_with_meta(h, h1, "key4", 4, 0, &itemMeta, 0,
+                      FORCE_ACCEPT_WITH_META_OPS, nullptr, validMetaVector);
+        checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS, last_status.load(),
+                "Expected success");
+    }
+
+    return SUCCESS;
+}
+
 // Test manifest //////////////////////////////////////////////////////////////
 
 const char *default_dbname = "./ep_testsuite_xdcr";
@@ -1892,6 +2122,18 @@ BaseTestCase testsuite_testcases[] = {
                  test_setting_drift_threshold, test_setup,
                  teardown, nullptr,
                  prepare, cleanup),
+        TestCase("test CAS regeneration lww",
+                 test_cas_regeneration, test_setup, teardown,
+                 "conflict_resolution_type=lww",
+                 prepare, cleanup),
+        TestCase("test CAS regeneration seqno",
+                 test_cas_regeneration, test_setup, teardown,
+                 "conflict_resolution_type=seqno",
+                 prepare, cleanup),
+        TestCase("test CAS options and nmeta",
+                 test_cas_options_and_nmeta, test_setup, teardown,
+                 "conflict_resolution_type=lww",
+                 prepare, cleanup),
 
         TestCase(NULL, NULL, NULL, NULL, NULL, prepare, cleanup)
 };