This is an automated email from the ASF dual-hosted git repository.
hulk pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks.git
The following commit(s) were added to refs/heads/unstable by this push:
new d5ad4973 Add the support of the restore command (#1684)
d5ad4973 is described below
commit d5ad497301aa0584af1fdc351593d3ee37e6472e
Author: hulk <[email protected]>
AuthorDate: Mon Aug 28 11:21:51 2023 +0800
Add the support of the restore command (#1684)
This PR will close #1648 which supports the restore command for making
Redis migration tools happy.
To achieve this feature, we need to implement all encoding types now
supported by Redis from 2.x to 7.x which includes:
- All kinds of data objects like String/List/Set/Hash/Sorted Set
- Binary encoding types: ZipMap/ZipList/IntSet/ListPack
For test cases, I have dumped value for Redis according to encoding types
and confirmed it works well
with the RedisShake migration tool. Another thing to be noticed, we haven't
supported the stream and module type yet,
because our stream type is partially implemented, so we can go back to
support it after all commands are supported.
Co-authored-by: Twice <[email protected]>
Co-authored-by: Twice <[email protected]>
---
src/commands/cmd_server.cc | 73 +++++
src/main.cc | 2 +
src/storage/rdb.cc | 451 ++++++++++++++++++++++++++++++
src/storage/rdb.h | 100 +++++++
src/storage/rdb_intset.cc | 88 ++++++
src/storage/rdb_intset.h | 38 +++
src/storage/rdb_listpack.cc | 222 +++++++++++++++
src/storage/rdb_listpack.h | 42 +++
src/storage/rdb_ziplist.cc | 154 ++++++++++
src/storage/rdb_ziplist.h | 44 +++
src/storage/rdb_zipmap.cc | 93 ++++++
src/storage/rdb_zipmap.h | 43 +++
src/vendor/crc64.cc | 379 +++++++++++++++++++++++++
src/vendor/crc64.h | 57 ++++
src/vendor/endianconv.cc | 90 ++++++
src/vendor/endianconv.h | 62 ++++
src/vendor/lzf.cc | 237 ++++++++++++++++
src/vendor/lzf.h | 200 +++++++++++++
tests/gocase/unit/restore/restore_test.go | 278 ++++++++++++++++++
tests/gocase/util/random.go | 4 +-
20 files changed, 2656 insertions(+), 1 deletion(-)
diff --git a/src/commands/cmd_server.cc b/src/commands/cmd_server.cc
index 95dd0086..7301432b 100644
--- a/src/commands/cmd_server.cc
+++ b/src/commands/cmd_server.cc
@@ -18,6 +18,7 @@
*
*/
+#include "command_parser.h"
#include "commander.h"
#include "commands/scan_base.h"
#include "common/io_util.h"
@@ -26,6 +27,7 @@
#include "server/redis_connection.h"
#include "server/server.h"
#include "stats/disk_stats.h"
+#include "storage/rdb.h"
#include "string_util.h"
#include "time_util.h"
@@ -978,6 +980,76 @@ static uint64_t GenerateConfigFlag(const
std::vector<std::string> &args) {
return 0;
}
+class CommandRestore : public Commander {
+ public:
+ Status Parse(const std::vector<std::string> &args) override {
+ CommandParser parser(args, 4);
+ ttl_ms_ = GET_OR_RET(ParseInt<int64_t>(args[2], {0, INT64_MAX}, 10));
+ while (parser.Good()) {
+ if (parser.EatEqICase("replace")) {
+ replace_ = true;
+ } else if (parser.EatEqICase("absttl")) {
+ absttl_ = true;
+ } else if (parser.EatEqICase("idletime")) {
+ // idle time is not supported in Kvrocks, so just skip it
+ auto idle_time = GET_OR_RET(parser.TakeInt());
+ if (idle_time < 0) {
+ return {Status::RedisParseErr, "IDLETIME can't be negative"};
+ }
+ } else if (parser.EatEqICase("freq")) {
+ // freq is not supported in Kvrocks, so just skip it
+ auto freq = GET_OR_RET(parser.TakeInt());
+ if (freq < 0 || freq > 255) {
+ return {Status::RedisParseErr, "FREQ must be >= 0 and <= 255"};
+ }
+ } else {
+ return {Status::RedisParseErr, errInvalidSyntax};
+ }
+ }
+ return Status::OK();
+ }
+
+ Status Execute(Server *svr, Connection *conn, std::string *output) override {
+ rocksdb::Status db_status;
+ redis::Database redis(svr->storage, conn->GetNamespace());
+ if (!replace_) {
+ int count = 0;
+ db_status = redis.Exists({args_[1]}, &count);
+ if (!db_status.ok()) {
+ return {Status::RedisExecErr, db_status.ToString()};
+ }
+ if (count > 0) {
+ return {Status::RedisExecErr, "target key name already exists."};
+ }
+ } else {
+ db_status = redis.Del(args_[1]);
+ if (!db_status.ok() && !db_status.IsNotFound()) {
+ return {Status::RedisExecErr, db_status.ToString()};
+ }
+ }
+ if (absttl_) {
+ auto now = util::GetTimeStampMS();
+ if (ttl_ms_ < now) {
+ // return ok if the ttl is already expired
+ *output = redis::SimpleString("OK");
+ return Status::OK();
+ }
+ ttl_ms_ -= now;
+ }
+
+ RDB rdb(svr->storage, conn->GetNamespace(), args_[3]);
+ auto s = rdb.Restore(args_[1], ttl_ms_);
+ if (!s.IsOK()) return {Status::RedisExecErr, s.Msg()};
+ *output = redis::SimpleString("OK");
+ return Status::OK();
+ }
+
+ private:
+ bool replace_ = false;
+ bool absttl_ = false;
+ uint64_t ttl_ms_ = 0;
+};
+
REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandAuth>("auth", 2, "read-only
ok-loading", 0, 0, 0),
MakeCmdAttr<CommandPing>("ping", -1, "read-only", 0,
0, 0),
MakeCmdAttr<CommandSelect>("select", 2, "read-only",
0, 0, 0),
@@ -1004,6 +1076,7 @@ REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandAuth>("auth",
2, "read-only ok-loadin
MakeCmdAttr<CommandDisk>("disk", 3, "read-only", 0, 0,
0),
MakeCmdAttr<CommandMemory>("memory", 3, "read-only",
0, 0, 0),
MakeCmdAttr<CommandHello>("hello", -1, "read-only
ok-loading", 0, 0, 0),
+ MakeCmdAttr<CommandRestore>("restore", -4, "write", 1,
1, 1),
MakeCmdAttr<CommandCompact>("compact", 1, "read-only
no-script", 0, 0, 0),
MakeCmdAttr<CommandBGSave>("bgsave", 1, "read-only
no-script", 0, 0, 0),
diff --git a/src/main.cc b/src/main.cc
index 4d8f8c22..1a2cefc5 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -47,6 +47,7 @@
#include "string_util.h"
#include "time_util.h"
#include "unique_fd.h"
+#include "vendor/crc64.h"
#include "version.h"
namespace google {
@@ -328,6 +329,7 @@ int main(int argc, char *argv[]) {
return 1;
}
+ crc64_init();
InitGoogleLog(&config);
LOG(INFO) << PrintVersion;
// Tricky: We don't expect that different instances running on the same port,
diff --git a/src/storage/rdb.cc b/src/storage/rdb.cc
new file mode 100644
index 00000000..5c8530c3
--- /dev/null
+++ b/src/storage/rdb.cc
@@ -0,0 +1,451 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "rdb.h"
+
+#include "common/encoding.h"
+#include "rdb_intset.h"
+#include "rdb_listpack.h"
+#include "rdb_ziplist.h"
+#include "rdb_zipmap.h"
+#include "time_util.h"
+#include "types/redis_hash.h"
+#include "types/redis_list.h"
+#include "types/redis_set.h"
+#include "types/redis_string.h"
+#include "types/redis_zset.h"
+#include "vendor/crc64.h"
+#include "vendor/endianconv.h"
+#include "vendor/lzf.h"
+
+// Redis object encoding length
+constexpr const int RDB6BitLen = 0;
+constexpr const int RDB14BitLen = 1;
+constexpr const int RDBEncVal = 3;
+constexpr const int RDB32BitLen = 0x08;
+constexpr const int RDB64BitLen = 0x81;
+constexpr const int RDBEncInt8 = 0;
+constexpr const int RDBEncInt16 = 1;
+constexpr const int RDBEncInt32 = 2;
+constexpr const int RDBEncLzf = 3;
+
+Status RDB::peekOk(size_t n) {
+ if (pos_ + n > input_.size()) {
+ return {Status::NotOK, "unexpected EOF"};
+ }
+ return Status::OK();
+}
+
+Status RDB::VerifyPayloadChecksum() {
+ if (input_.size() < 10) {
+ return {Status::NotOK, "invalid payload length"};
+ }
+ auto footer = input_.substr(input_.size() - 10);
+ auto rdb_version = (footer[1] << 8) | footer[0];
+ // For now, the max redis rdb version is 10
+ if (rdb_version > 11) {
+ return {Status::NotOK, fmt::format("invalid version: {}", rdb_version)};
+ }
+ uint64_t crc = crc64(0, reinterpret_cast<const unsigned char
*>(input_.data()), input_.size() - 8);
+ memrev64ifbe(&crc);
+ if (memcmp(&crc, footer.data() + 2, 8)) {
+ return {Status::NotOK, "incorrect checksum"};
+ }
+ return Status::OK();
+}
+
+StatusOr<int> RDB::LoadObjectType() {
+ GET_OR_RET(peekOk(1));
+ auto type = input_[pos_++] & 0xFF;
+ // 0-5 is the basic type of Redis objects and 9-21 is the encoding type of
Redis objects.
+ // Redis allow basic is 0-7 and 6/7 is for the module type which we don't
support here.
+ if ((type >= 0 && type < 5) || (type >= 9 && type <= 21)) {
+ return type;
+ }
+ return {Status::NotOK, fmt::format("invalid object type: {}", type)};
+}
+
+StatusOr<uint64_t> RDB::loadObjectLen(bool *is_encoded) {
+ GET_OR_RET(peekOk(1));
+ uint64_t len = 0;
+ auto c = input_[pos_++];
+ auto type = (c & 0xC0) >> 6;
+ switch (type) {
+ case RDBEncVal:
+ if (is_encoded) *is_encoded = true;
+ return c & 0x3F;
+ case RDB6BitLen:
+ return c & 0x3F;
+ case RDB14BitLen:
+ len = c & 0x3F;
+ GET_OR_RET(peekOk(1));
+ return (len << 8) | input_[pos_++];
+ case RDB32BitLen:
+ GET_OR_RET(peekOk(4));
+ __builtin_memcpy(&len, input_.data() + pos_, 4);
+ pos_ += 4;
+ return len;
+ case RDB64BitLen:
+ GET_OR_RET(peekOk(8));
+ __builtin_memcpy(&len, input_.data() + pos_, 8);
+ pos_ += 8;
+ return len;
+ default:
+ return {Status::NotOK, fmt::format("Unknown length encoding {} in
loadObjectLen()", type)};
+ }
+}
+
+StatusOr<std::string> RDB::LoadStringObject() { return loadEncodedString(); }
+
+// Load the LZF compression string
+StatusOr<std::string> RDB::loadLzfString() {
+ auto compression_len = GET_OR_RET(loadObjectLen(nullptr));
+ auto len = GET_OR_RET(loadObjectLen(nullptr));
+ GET_OR_RET(peekOk(static_cast<size_t>(compression_len)));
+
+ std::string out_buf(len, 0);
+ if (lzf_decompress(input_.data() + pos_, compression_len, out_buf.data(),
len) != len) {
+ return {Status::NotOK, "Invalid LZF compressed string"};
+ }
+ pos_ += compression_len;
+ return out_buf;
+}
+
+StatusOr<std::string> RDB::loadEncodedString() {
+ bool is_encoded = false;
+ auto len = GET_OR_RET(loadObjectLen(&is_encoded));
+ if (is_encoded) {
+ // For integer type, needs to convert to uint8_t* to avoid signed extension
+ auto data = reinterpret_cast<const uint8_t *>(input_.data());
+ if (len == RDBEncInt8) {
+ GET_OR_RET(peekOk(1));
+ return std::to_string(data[pos_++]);
+ } else if (len == RDBEncInt16) {
+ GET_OR_RET(peekOk(2));
+ auto value = static_cast<uint16_t>(data[pos_]) |
(static_cast<uint16_t>(data[pos_ + 1]) << 8);
+ pos_ += 2;
+ return std::to_string(static_cast<int16_t>(value));
+ } else if (len == RDBEncInt32) {
+ GET_OR_RET(peekOk(4));
+ auto value = static_cast<uint32_t>(data[pos_]) |
(static_cast<uint32_t>(data[pos_ + 1]) << 8) |
+ (static_cast<uint32_t>(data[pos_ + 2]) << 16) |
(static_cast<uint32_t>(data[pos_ + 3]) << 24);
+ pos_ += 4;
+ return std::to_string(static_cast<int32_t>(value));
+ } else if (len == RDBEncLzf) {
+ return loadLzfString();
+ } else {
+ return {Status::NotOK, fmt::format("Unknown RDB string encoding type
{}", len)};
+ }
+ }
+
+ // Normal string
+ GET_OR_RET(peekOk(static_cast<size_t>(len)));
+ auto value = std::string(input_.data() + pos_, len);
+ pos_ += len;
+ return value;
+}
+
+StatusOr<std::vector<std::string>> RDB::LoadListWithQuickList(int type) {
+ std::vector<std::string> list;
+ auto len = GET_OR_RET(loadObjectLen(nullptr));
+ if (len == 0) {
+ return list;
+ }
+
+ uint64_t container = QuickListNodeContainerPacked;
+ for (size_t i = 0; i < len; i++) {
+ if (type == RDBTypeListQuickList2) {
+ container = GET_OR_RET(loadObjectLen(nullptr));
+ if (container != QuickListNodeContainerPlain && container !=
QuickListNodeContainerPacked) {
+ return {Status::NotOK, fmt::format("Unknown quicklist node container
type {}", container)};
+ }
+ }
+ auto list_pack_string = GET_OR_RET(loadEncodedString());
+ ListPack lp(list_pack_string);
+ auto elements = GET_OR_RET(lp.Entries());
+ list.insert(list.end(), elements.begin(), elements.end());
+ }
+ return list;
+}
+
+StatusOr<std::vector<std::string>> RDB::LoadListObject() {
+ auto len = GET_OR_RET(loadObjectLen(nullptr));
+ std::vector<std::string> list;
+ if (len == 0) {
+ return list;
+ }
+ for (size_t i = 0; i < len; i++) {
+ auto element = GET_OR_RET(loadEncodedString());
+ list.push_back(element);
+ }
+ return list;
+}
+
+StatusOr<std::vector<std::string>> RDB::LoadListWithZipList() {
+ auto encoded_string = GET_OR_RET(LoadStringObject());
+ ZipList zip_list(encoded_string);
+ return zip_list.Entries();
+}
+
+StatusOr<std::vector<std::string>> RDB::LoadSetObject() {
+ auto len = GET_OR_RET(loadObjectLen(nullptr));
+ std::vector<std::string> set;
+ if (len == 0) {
+ return set;
+ }
+ for (size_t i = 0; i < len; i++) {
+ auto element = GET_OR_RET(LoadStringObject());
+ set.push_back(element);
+ }
+ return set;
+}
+
+StatusOr<std::vector<std::string>> RDB::LoadSetWithListPack() {
+ auto encoded_string = GET_OR_RET(LoadStringObject());
+ ListPack lp(encoded_string);
+ return lp.Entries();
+}
+
+StatusOr<std::vector<std::string>> RDB::LoadSetWithIntSet() {
+ auto encoded_string = GET_OR_RET(LoadStringObject());
+ IntSet intset(encoded_string);
+ return intset.Entries();
+}
+
+StatusOr<std::map<std::string, std::string>> RDB::LoadHashObject() {
+ auto len = GET_OR_RET(loadObjectLen(nullptr));
+ std::map<std::string, std::string> hash;
+ if (len == 0) {
+ return hash;
+ }
+
+ for (size_t i = 0; i < len; i++) {
+ auto field = GET_OR_RET(LoadStringObject());
+ auto value = GET_OR_RET(LoadStringObject());
+ hash[field] = value;
+ }
+ return hash;
+}
+
+StatusOr<std::map<std::string, std::string>> RDB::LoadHashWithZipMap() {
+ auto encoded_string = GET_OR_RET(LoadStringObject());
+ ZipMap zip_map(encoded_string);
+ return zip_map.Entries();
+}
+
+StatusOr<std::map<std::string, std::string>> RDB::LoadHashWithListPack() {
+ auto encoded_string = GET_OR_RET(LoadStringObject());
+ ListPack lp(encoded_string);
+ auto entries = GET_OR_RET(lp.Entries());
+ if (entries.size() % 2 != 0) {
+ return {Status::NotOK, "invalid list pack length"};
+ }
+ std::map<std::string, std::string> hash;
+ for (size_t i = 0; i < entries.size(); i += 2) {
+ hash[entries[i]] = entries[i + 1];
+ }
+ return hash;
+}
+
+StatusOr<std::map<std::string, std::string>> RDB::LoadHashWithZipList() {
+ auto encoded_string = GET_OR_RET(LoadStringObject());
+ ZipList zip_list(encoded_string);
+ auto entries = GET_OR_RET(zip_list.Entries());
+ if (entries.size() % 2 != 0) {
+ return {Status::NotOK, "invalid zip list length"};
+ }
+ std::map<std::string, std::string> hash;
+ for (size_t i = 0; i < entries.size(); i += 2) {
+ hash[entries[i]] = entries[i + 1];
+ }
+ return hash;
+}
+
+StatusOr<double> RDB::loadBinaryDouble() {
+ GET_OR_RET(peekOk(8));
+ double value = 0;
+ memcpy(&value, input_.data() + pos_, 8);
+ memrev64ifbe(&value);
+ pos_ += 8;
+ return value;
+}
+
+StatusOr<double> RDB::loadDouble() {
+ char buf[256];
+ GET_OR_RET(peekOk(1));
+ auto len = static_cast<uint8_t>(input_[pos_++]);
+ switch (len) {
+ case 255:
+ return -INFINITY; /* Negative inf */
+ case 254:
+ return INFINITY; /* Positive inf */
+ case 253:
+ return NAN; /* NaN */
+ }
+ GET_OR_RET(peekOk(len));
+ memcpy(buf, input_.data() + pos_, len);
+ buf[len] = '\0';
+ pos_ += len;
+ return ParseFloat<double>(std::string(buf, len));
+}
+
+StatusOr<std::vector<MemberScore>> RDB::LoadZSetObject(int type) {
+ auto len = GET_OR_RET(loadObjectLen(nullptr));
+ std::vector<MemberScore> zset;
+ if (len == 0) {
+ return zset;
+ }
+
+ for (size_t i = 0; i < len; i++) {
+ auto member = GET_OR_RET(LoadStringObject());
+ double score = 0;
+ if (type == RDBTypeZSet2) {
+ score = GET_OR_RET(loadBinaryDouble());
+ } else {
+ score = GET_OR_RET(loadDouble());
+ }
+ zset.emplace_back(MemberScore{member, score});
+ }
+ return zset;
+}
+
+StatusOr<std::vector<MemberScore>> RDB::LoadZSetWithListPack() {
+ auto encoded_string = GET_OR_RET(LoadStringObject());
+ ListPack lp(encoded_string);
+ auto entries = GET_OR_RET(lp.Entries());
+ if (entries.size() % 2 != 0) {
+ return {Status::NotOK, "invalid list pack length"};
+ }
+ std::vector<MemberScore> zset;
+ for (size_t i = 0; i < entries.size(); i += 2) {
+ auto score = GET_OR_RET(ParseFloat<double>(entries[i + 1]));
+ zset.emplace_back(MemberScore{entries[i], score});
+ }
+ return zset;
+}
+
+StatusOr<std::vector<MemberScore>> RDB::LoadZSetWithZipList() {
+ auto encoded_string = GET_OR_RET(LoadStringObject());
+ ZipList zip_list(encoded_string);
+ auto entries = GET_OR_RET(zip_list.Entries());
+ if (entries.size() % 2 != 0) {
+ return {Status::NotOK, "invalid zip list length"};
+ }
+ std::vector<MemberScore> zset;
+ for (size_t i = 0; i < entries.size(); i += 2) {
+ auto score = GET_OR_RET(ParseFloat<double>(entries[i + 1]));
+ zset.emplace_back(MemberScore{entries[i], score});
+ }
+ return zset;
+}
+
+Status RDB::Restore(const std::string &key, uint64_t ttl_ms) {
+ rocksdb::Status db_status;
+
+ // Check the checksum of the payload
+ GET_OR_RET(VerifyPayloadChecksum());
+
+ auto type = GET_OR_RET(LoadObjectType());
+ if (type == RDBTypeString) {
+ auto value = GET_OR_RET(LoadStringObject());
+ redis::String string_db(storage_, ns_);
+ db_status = string_db.SetEX(key, value, ttl_ms);
+ } else if (type == RDBTypeSet || type == RDBTypeSetIntSet || type ==
RDBTypeSetListPack) {
+ std::vector<std::string> members;
+ if (type == RDBTypeSet) {
+ members = GET_OR_RET(LoadSetObject());
+ } else if (type == RDBTypeSetListPack) {
+ members = GET_OR_RET(LoadSetWithListPack());
+ } else {
+ members = GET_OR_RET(LoadSetWithIntSet());
+ }
+ redis::Set set_db(storage_, ns_);
+ uint64_t count = 0;
+ std::vector<Slice> insert_members;
+ insert_members.reserve(members.size());
+ for (const auto &member : members) {
+ insert_members.emplace_back(member);
+ }
+ db_status = set_db.Add(key, insert_members, &count);
+ } else if (type == RDBTypeZSet || type == RDBTypeZSet2 || type ==
RDBTypeZSetListPack || type == RDBTypeZSetZipList) {
+ std::vector<MemberScore> member_scores;
+ if (type == RDBTypeZSet || type == RDBTypeZSet2) {
+ member_scores = GET_OR_RET(LoadZSetObject(type));
+ } else if (type == RDBTypeZSetListPack) {
+ member_scores = GET_OR_RET(LoadZSetWithListPack());
+ } else {
+ member_scores = GET_OR_RET(LoadZSetWithZipList());
+ }
+ redis::ZSet zset_db(storage_, ns_);
+ uint64_t count = 0;
+ db_status = zset_db.Add(key, ZAddFlags(0), (redis::ZSet::MemberScores
*)&member_scores, &count);
+ } else if (type == RDBTypeHash || type == RDBTypeHashListPack || type ==
RDBTypeHashZipList ||
+ type == RDBTypeHashZipMap) {
+ std::map<std::string, std::string> entries;
+ if (type == RDBTypeHash) {
+ entries = GET_OR_RET(LoadHashObject());
+ } else if (type == RDBTypeHashListPack) {
+ entries = GET_OR_RET(LoadHashWithListPack());
+ } else if (type == RDBTypeHashZipList) {
+ entries = GET_OR_RET(LoadHashWithZipList());
+ } else {
+ entries = GET_OR_RET(LoadHashWithZipMap());
+ }
+ std::vector<FieldValue> filed_values;
+ filed_values.reserve(entries.size());
+ for (const auto &entry : entries) {
+ filed_values.emplace_back(entry.first, entry.second);
+ }
+ redis::Hash hash_db(storage_, ns_);
+ uint64_t count = 0;
+ db_status = hash_db.MSet(key, filed_values, false /*nx*/, &count);
+ } else if (type == RDBTypeList || type == RDBTypeListZipList || type ==
RDBTypeListQuickList ||
+ type == RDBTypeListQuickList2) {
+ std::vector<std::string> elements;
+ if (type == RDBTypeList) {
+ elements = GET_OR_RET(LoadListObject());
+ } else if (type == RDBTypeListZipList) {
+ elements = GET_OR_RET(LoadListWithZipList());
+ } else {
+ elements = GET_OR_RET(LoadListWithQuickList(type));
+ }
+ if (!elements.empty()) {
+ std::vector<Slice> insert_elements;
+ insert_elements.reserve(elements.size());
+ for (const auto &element : elements) {
+ insert_elements.emplace_back(element);
+ }
+ redis::List list_db(storage_, ns_);
+ uint64_t list_size = 0;
+ db_status = list_db.Push(key, insert_elements, false, &list_size);
+ }
+ } else {
+ return {Status::RedisExecErr, fmt::format("unsupported restore type: {}",
type)};
+ }
+ if (!db_status.ok()) {
+ return {Status::RedisExecErr, db_status.ToString()};
+ }
+ // String type will use the SETEX, so just only set the ttl for other types
+ if (ttl_ms > 0 && type != RDBTypeString) {
+ redis::Database db(storage_, ns_);
+ db_status = db.Expire(key, ttl_ms + util::GetTimeStampMS());
+ }
+ return db_status.ok() ? Status::OK() : Status{Status::RedisExecErr,
db_status.ToString()};
+}
diff --git a/src/storage/rdb.h b/src/storage/rdb.h
new file mode 100644
index 00000000..2ba306b5
--- /dev/null
+++ b/src/storage/rdb.h
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#pragma once
+
+#include <string_view>
+#include <utility>
+
+#include "status.h"
+#include "types/redis_zset.h"
+
+// Redis object type
+constexpr const int RDBTypeString = 0;
+constexpr const int RDBTypeList = 1;
+constexpr const int RDBTypeSet = 2;
+constexpr const int RDBTypeZSet = 3;
+constexpr const int RDBTypeHash = 4;
+constexpr const int RDBTypeZSet2 = 5;
+
+// Redis object encoding
+constexpr const int RDBTypeHashZipMap = 9;
+constexpr const int RDBTypeListZipList = 10;
+constexpr const int RDBTypeSetIntSet = 11;
+constexpr const int RDBTypeZSetZipList = 12;
+constexpr const int RDBTypeHashZipList = 13;
+constexpr const int RDBTypeListQuickList = 14;
+constexpr const int RDBTypeStreamListPack = 15;
+constexpr const int RDBTypeHashListPack = 16;
+constexpr const int RDBTypeZSetListPack = 17;
+constexpr const int RDBTypeListQuickList2 = 18;
+constexpr const int RDBTypeStreamListPack2 = 19;
+constexpr const int RDBTypeSetListPack = 20;
+
+// Quick list node encoding
+constexpr const int QuickListNodeContainerPlain = 1;
+constexpr const int QuickListNodeContainerPacked = 2;
+
+class RDB {
+ public:
+ explicit RDB(engine::Storage *storage, std::string ns, std::string_view
input)
+ : storage_(storage), ns_(std::move(ns)), input_(input){};
+ ~RDB() = default;
+
+ Status VerifyPayloadChecksum();
+ StatusOr<int> LoadObjectType();
+ Status Restore(const std::string &key, uint64_t ttl_ms);
+
+ // String
+ StatusOr<std::string> LoadStringObject();
+
+ // List
+ StatusOr<std::map<std::string, std::string>> LoadHashObject();
+ StatusOr<std::map<std::string, std::string>> LoadHashWithZipMap();
+ StatusOr<std::map<std::string, std::string>> LoadHashWithListPack();
+ StatusOr<std::map<std::string, std::string>> LoadHashWithZipList();
+
+ // Sorted Set
+ StatusOr<std::vector<MemberScore>> LoadZSetObject(int type);
+ StatusOr<std::vector<MemberScore>> LoadZSetWithListPack();
+ StatusOr<std::vector<MemberScore>> LoadZSetWithZipList();
+
+ // Set
+ StatusOr<std::vector<std::string>> LoadSetObject();
+ StatusOr<std::vector<std::string>> LoadSetWithIntSet();
+ StatusOr<std::vector<std::string>> LoadSetWithListPack();
+
+ // List
+ StatusOr<std::vector<std::string>> LoadListObject();
+ StatusOr<std::vector<std::string>> LoadListWithZipList();
+ StatusOr<std::vector<std::string>> LoadListWithQuickList(int type);
+
+ private:
+ engine::Storage *storage_;
+ std::string ns_;
+ std::string_view input_;
+ size_t pos_ = 0;
+
+ StatusOr<std::string> loadLzfString();
+ StatusOr<std::string> loadEncodedString();
+ StatusOr<uint64_t> loadObjectLen(bool *is_encoded);
+ Status peekOk(size_t n);
+ StatusOr<double> loadBinaryDouble();
+ StatusOr<double> loadDouble();
+};
diff --git a/src/storage/rdb_intset.cc b/src/storage/rdb_intset.cc
new file mode 100644
index 00000000..58431f65
--- /dev/null
+++ b/src/storage/rdb_intset.cc
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "rdb_intset.h"
+
+#include "vendor/endianconv.h"
+
+constexpr const uint32_t IntSetHeaderSize = 8;
+
+constexpr const uint32_t IntSetEncInt16 = (sizeof(int16_t));
+constexpr const uint32_t IntSetEncInt32 = (sizeof(int32_t));
+constexpr const uint32_t IntSetEncInt64 = (sizeof(int64_t));
+
+Status IntSet::peekOk(size_t n) {
+ if (pos_ + n > input_.size()) {
+ return {Status::NotOK, "reach the end of intset"};
+ }
+ return Status::OK();
+}
+
+StatusOr<std::vector<std::string>> IntSet::Entries() {
+ GET_OR_RET(peekOk(IntSetHeaderSize));
+ uint32_t encoding = 0, len = 0;
+ memcpy(&encoding, input_.data() + pos_, sizeof(uint32_t));
+ pos_ += sizeof(uint32_t);
+ intrev32ifbe(&encoding);
+ memcpy(&len, input_.data() + pos_, sizeof(uint32_t));
+ pos_ += sizeof(uint32_t);
+ intrev32ifbe(&len);
+
+ uint32_t record_size = encoding;
+ if (record_size == 0) {
+ return {Status::NotOK, "invalid intset encoding"};
+ }
+ if (IntSetHeaderSize + len * record_size != input_.size()) {
+ return {Status::NotOK, "invalid intset length"};
+ }
+
+ std::vector<std::string> entries;
+ for (uint32_t i = 0; i < len; i++) {
+ switch (encoding) {
+ case IntSetEncInt16: {
+ uint16_t v = 0;
+ memcpy(&v, input_.data() + pos_, sizeof(uint16_t));
+ pos_ += sizeof(uint16_t);
+ intrev16ifbe(&v);
+ entries.emplace_back(std::to_string(v));
+ break;
+ }
+ case IntSetEncInt32: {
+ uint32_t v = 0;
+ memcpy(&v, input_.data() + pos_, sizeof(uint32_t));
+ pos_ += sizeof(uint32_t);
+ intrev32ifbe(&v);
+ entries.emplace_back(std::to_string(v));
+ break;
+ }
+ case IntSetEncInt64: {
+ uint64_t v = 0;
+ memcpy(&v, input_.data() + pos_, sizeof(uint64_t));
+ pos_ += sizeof(uint64_t);
+ intrev64ifbe(&v);
+ entries.emplace_back(std::to_string(v));
+ break;
+ }
+ default:
+ return {Status::NotOK, "invalid intset encoding"};
+ }
+ }
+ return entries;
+}
diff --git a/src/storage/rdb_intset.h b/src/storage/rdb_intset.h
new file mode 100644
index 00000000..627615e5
--- /dev/null
+++ b/src/storage/rdb_intset.h
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#pragma once
+
+#include <string_view>
+
+#include "common/status.h"
+
+class IntSet {
+ public:
+ explicit IntSet(std::string_view input) : input_(input){};
+ ~IntSet() = default;
+ StatusOr<std::vector<std::string>> Entries();
+
+ private:
+ std::string_view input_;
+ uint64_t pos_ = 0;
+
+ Status peekOk(size_t n);
+};
diff --git a/src/storage/rdb_listpack.cc b/src/storage/rdb_listpack.cc
new file mode 100644
index 00000000..f2933bb9
--- /dev/null
+++ b/src/storage/rdb_listpack.cc
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/*
+ * ListPack implements the reading of list-pack binary format
+ * according to: https://github.com/antirez/listpack/blob/master/listpack.md.
+ *
+ * A list-pack is encoded into a single linear chunk of memory.
+ * It has a fixed length header of six bytes which contains the total bytes
and number of elements:
+ *
+ * <total-bytes> <num-elements> <element-1> ... <element-N> <end-byte>
+ * | |
+ * 4byte 2byte
+ * Each element is encoded as follows:
+ *
+ * <encoding-type><element-data><element-total-bytes>
+ *
+ * encoding-type is used to indicate what type of data is stored in the
element-data:
+ * - [0xxxxxxx] represents 7bit unsigned int
+ * - [10xxxxxx] represents 6bit string, the first byte is the length of the
string
+ * - [110xxxxx] represents 13bit signed integer, the first byte is the sign
bit
+ * - [1110|xxxx yyyyyyyy] represents 12bit string which can up to 4095 bytes
+ * - [1111|0000] represents 32bit string and the next 4 bytes are the length
of the string
+ * - [1111|0001] represents 16 bits signed integer
+ * - [1111|0010] represents 24 bits signed integer
+ * - [1111|0011] represents 32 bits signed integer
+ * - [1111|0100] represents 64 bits signed integer
+ * - [1111|0101] not used for now
+ * - [1111|1111] represents the end of the list-pack
+ *
+ * element-data was determined by the encoding-type and element-total-bytes is
the total bytes of the element.
+ * The total bytes of the element is used to traverse the list-pack from the
end to the beginning.
+ */
+#include "rdb_listpack.h"
+
+constexpr const int ListPack7BitUIntMask = 0x80;
+constexpr const int ListPack7BitUInt = 0;
+// skip 1byte for encoding type and 1byte for element length
+constexpr const int ListPack7BitIntEntrySize = 2;
+
+constexpr const int ListPack6BitStringMask = 0xC0;
+constexpr const int ListPack6BitString = 0x80;
+
+constexpr const int ListPack13BitIntMask = 0xE0;
+constexpr const int ListPack13BitInt = 0xC0;
+// skip 2byte for encoding type and 1byte for element length
+constexpr const int ListPack13BitIntEntrySize = 3;
+
+constexpr const int ListPack12BitStringMask = 0xF0;
+constexpr const int ListPack12BitString = 0xE0;
+
+constexpr const int ListPack16BitIntMask = 0xFF;
+constexpr const int ListPack16BitInt = 0xF1;
+// skip 3byte for encoding type and 1byte for element length
+constexpr const int ListPack16BitIntEntrySize = 4;
+
+constexpr const int ListPack24BitIntMask = 0xFF;
+constexpr const int ListPack24BitInt = 0xF2;
+// skip 4byte for encoding type and 1byte for element length
+constexpr const int ListPack24BitIntEntrySize = 5;
+
+constexpr const int ListPack32BitIntMask = 0xFF;
+constexpr const int ListPack32BitInt = 0xF3;
+// skip 5byte for encoding type and 1byte for element length
+constexpr const int ListPack32BitIntEntrySize = 6;
+
+constexpr const int ListPack64BitIntMask = 0xFF;
+constexpr const int ListPack64BitInt = 0xF4;
+// skip 9byte for encoding type and 1byte for element length
+constexpr const int ListPack64BitIntEntrySize = 10;
+
+constexpr const int ListPack32BitStringMask = 0xFF;
+constexpr const int ListPack32BitString = 0xF0;
+
+constexpr const int ListPackEOF = 0xFF;
+
+StatusOr<std::vector<std::string>> ListPack::Entries() {
+ auto len = GET_OR_RET(Length());
+ std::vector<std::string> elements;
+ while (len-- != 0) {
+ auto element = GET_OR_RET(Next());
+ elements.emplace_back(element);
+ }
+ return elements;
+}
+
+StatusOr<uint32_t> ListPack::Length() {
+ // list pack header size is 6 bytes which contains the total bytes and
number of elements
+ constexpr const int listPackHeaderSize = 6;
+ if (input_.size() < listPackHeaderSize) {
+ return {Status::NotOK, "invalid listpack length"};
+ }
+
+ // total bytes and number of elements are encoded in little endian
+ uint32_t total_bytes = (static_cast<uint32_t>(input_[0])) |
(static_cast<uint32_t>(input_[1]) << 8) |
+ (static_cast<uint32_t>(input_[2]) << 16) |
(static_cast<uint32_t>(input_[3]) << 24);
+ uint32_t len = (static_cast<uint32_t>(input_[4])) |
(static_cast<uint32_t>(input_[5]) << 8);
+ pos_ += listPackHeaderSize;
+
+ if (total_bytes != input_.size()) {
+ return {Status::NotOK, "invalid listpack length"};
+ }
+ return len;
+}
+
+// encodeBackLen returns the number of bytes required to encode the length of
the element.
+uint32_t ListPack::encodeBackLen(uint32_t len) {
+ if (len <= 127) {
+ return 1;
+ } else if (len < 16383) {
+ return 2;
+ } else if (len < 2097151) {
+ return 3;
+ } else if (len < 268435455) {
+ return 4;
+ } else {
+ return 5;
+ }
+}
+
+Status ListPack::peekOK(size_t n) {
+ if (pos_ + n > input_.size()) {
+ return {Status::NotOK, "reach the end of listpack"};
+ }
+ return Status::OK();
+}
+
+StatusOr<std::string> ListPack::Next() {
+ GET_OR_RET(peekOK(1));
+
+ uint32_t value_len = 0;
+ uint64_t int_value = 0;
+ std::string value;
+ // For integer type, need to convert the byte to the uint8_t type to avoid
the sign extension.
+ auto data = reinterpret_cast<const uint8_t*>(input_.data());
+ auto c = data[pos_];
+ if ((c & ListPack7BitUIntMask) == ListPack7BitUInt) { // 7bit unsigned int
+ GET_OR_RET(peekOK(2));
+ int_value = c & 0x7F;
+ value = std::to_string(int_value);
+ pos_ += ListPack7BitIntEntrySize;
+ } else if ((c & ListPack6BitStringMask) == ListPack6BitString) { // 6bit
string
+ value_len = (c & 0x3F);
+ // skip the encoding type byte
+ pos_ += 1;
+ GET_OR_RET(peekOK(value_len));
+ value = input_.substr(pos_, value_len);
+ // skip the value bytes and the length of the element
+ pos_ += value_len + encodeBackLen(value_len + 1);
+ } else if ((c & ListPack13BitIntMask) == ListPack13BitInt) { // 13bit int
+ GET_OR_RET(peekOK(3));
+ int_value = ((c & 0x1F) << 8) | data[pos_];
+ value = std::to_string(int_value);
+ pos_ += ListPack13BitIntEntrySize;
+ } else if ((c & ListPack16BitIntMask) == ListPack16BitInt) { // 16bit int
+ GET_OR_RET(peekOK(4));
+ int_value = (static_cast<uint64_t>(data[pos_ + 1])) |
(static_cast<uint64_t>(data[pos_ + 2]) << 8);
+ value = std::to_string(int_value);
+ pos_ += ListPack16BitIntEntrySize;
+ } else if ((c & ListPack24BitIntMask) == ListPack24BitInt) { // 24bit int
+ GET_OR_RET(peekOK(5));
+ int_value = (static_cast<uint64_t>(data[pos_ + 1])) |
(static_cast<uint64_t>(data[pos_ + 2]) << 8) |
+ (static_cast<uint64_t>(data[pos_ + 3]) << 16);
+ value = std::to_string(int_value);
+ pos_ += ListPack24BitIntEntrySize;
+ } else if ((c & ListPack32BitIntMask) == ListPack32BitInt) { // 32bit int
+ GET_OR_RET(peekOK(6));
+ int_value = (static_cast<uint64_t>(data[pos_ + 1])) |
(static_cast<uint64_t>(data[pos_ + 2]) << 8) |
+ (static_cast<uint64_t>(data[pos_ + 3]) << 16) |
(static_cast<uint64_t>(data[pos_ + 4]) << 24);
+ value = std::to_string(int_value);
+ pos_ += ListPack32BitIntEntrySize;
+ } else if ((c & ListPack64BitIntMask) == ListPack64BitInt) { // 64bit int
+ GET_OR_RET(peekOK(10));
+ int_value = (static_cast<uint64_t>(data[pos_ + 1])) |
(static_cast<uint64_t>(input_[pos_ + 2]) << 8) |
+ (static_cast<uint64_t>(data[pos_ + 3]) << 16) |
(static_cast<uint64_t>(data[pos_ + 4]) << 24) |
+ (static_cast<uint64_t>(data[pos_ + 5]) << 32) |
(static_cast<uint64_t>(data[pos_ + 6]) << 40) |
+ (static_cast<uint64_t>(data[pos_ + 7]) << 48) |
(static_cast<uint64_t>(data[pos_ + 8]) << 56);
+ value = std::to_string(int_value);
+ pos_ += ListPack64BitIntEntrySize;
+ } else if ((c & ListPack12BitStringMask) == ListPack12BitString) { // 12bit
string
+ GET_OR_RET(peekOK(2));
+ value_len = ((data[pos_] & 0xF) << 8) | data[pos_ + 1];
+ // skip 2byte encoding type
+ pos_ += 2;
+ GET_OR_RET(peekOK(value_len));
+ value = input_.substr(pos_, value_len);
+ // skip the value bytes and the length of the element
+ pos_ += value_len + encodeBackLen(value_len + 2);
+ } else if ((c & ListPack32BitStringMask) == ListPack32BitString) { // 32bit
string
+ GET_OR_RET(peekOK(5));
+ value_len = (static_cast<uint32_t>(data[pos_])) |
(static_cast<uint32_t>(data[pos_ + 1]) << 8) |
+ (static_cast<uint32_t>(data[pos_ + 2]) << 16) |
(static_cast<uint32_t>(data[pos_ + 3]) << 24);
+ // skip 5byte encoding type
+ pos_ += 5;
+ GET_OR_RET(peekOK(value_len));
+ value = input_.substr(pos_, value_len);
+ // skip the value bytes and the length of the element
+ pos_ += value_len + encodeBackLen(value_len + 5);
+ } else if (c == ListPackEOF) {
+ ++pos_;
+ } else {
+ return {Status::NotOK, "invalid listpack entry"};
+ }
+ return value;
+}
diff --git a/src/storage/rdb_listpack.h b/src/storage/rdb_listpack.h
new file mode 100644
index 00000000..9087769c
--- /dev/null
+++ b/src/storage/rdb_listpack.h
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#pragma once
+
+#include <string_view>
+
+#include "common/status.h"
+
+class ListPack {
+ public:
+ explicit ListPack(std::string_view input) : input_(input){};
+ ~ListPack() = default;
+
+ StatusOr<uint32_t> Length();
+ StatusOr<std::string> Next();
+ StatusOr<std::vector<std::string>> Entries();
+
+ private:
+ std::string_view input_;
+ uint64_t pos_ = 0;
+
+ Status peekOK(size_t n);
+ static uint32_t encodeBackLen(uint32_t len);
+};
diff --git a/src/storage/rdb_ziplist.cc b/src/storage/rdb_ziplist.cc
new file mode 100644
index 00000000..3d7cfd84
--- /dev/null
+++ b/src/storage/rdb_ziplist.cc
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "rdb_ziplist.h"
+
+#include "vendor/endianconv.h"
+
+constexpr const int zlHeaderSize = 10;
+constexpr const uint8_t ZipListBigLen = 0xFE;
+constexpr const uint8_t zlEnd = 0xFF;
+
+constexpr const uint8_t ZIP_STR_MASK = 0xC0;
+constexpr const uint8_t ZIP_STR_06B = (0 << 6);
+constexpr const uint8_t ZIP_STR_14B = (1 << 6);
+constexpr const uint8_t ZIP_STR_32B = (2 << 6);
+constexpr const uint8_t ZIP_INT_16B = (0xC0 | 0 << 4);
+constexpr const uint8_t ZIP_INT_32B = (0xC0 | 1 << 4);
+constexpr const uint8_t ZIP_INT_64B = (0xC0 | 2 << 4);
+constexpr const uint8_t ZIP_INT_24B = (0xC0 | 3 << 4);
+constexpr const uint8_t ZIP_INT_8B = 0xFE;
+
+constexpr const uint8_t ZIP_INT_IMM_MIN = 0xF1; /* 11110001 */
+constexpr const uint8_t ZIP_INT_IMM_MAX = 0xFD; /* 11111101 */
+
+StatusOr<std::string> ZipList::Next() {
+ auto prev_entry_encoded_size = getEncodedLengthSize(pre_entry_len_);
+ pos_ += prev_entry_encoded_size;
+ GET_OR_RET(peekOK(1));
+ auto encoding = static_cast<uint8_t>(input_[pos_]);
+ if (encoding < ZIP_STR_MASK) {
+ encoding &= ZIP_STR_MASK;
+ }
+
+ uint32_t len = 0, len_bytes = 0;
+ std::string value;
+ if ((encoding) < ZIP_STR_MASK) {
+ // For integer type, needs to convert to uint8_t* to avoid signed extension
+ auto data = reinterpret_cast<const uint8_t*>(input_.data());
+ if ((encoding) == ZIP_STR_06B) {
+ len_bytes = 1;
+ len = data[pos_] & 0x3F;
+ } else if ((encoding) == ZIP_STR_14B) {
+ GET_OR_RET(peekOK(2));
+ len_bytes = 2;
+ len = ((static_cast<uint32_t>(data[pos_]) & 0x3F) << 8) |
static_cast<uint32_t>(data[pos_ + 1]);
+ } else if ((encoding) == ZIP_STR_32B) {
+ GET_OR_RET(peekOK(5));
+ len_bytes = 5;
+ len = (static_cast<uint32_t>(data[pos_]) << 24) |
(static_cast<uint32_t>(data[pos_ + 1]) << 16) |
+ (static_cast<uint32_t>(data[pos_ + 2]) << 8) |
static_cast<uint32_t>(data[pos_ + 3]);
+ } else {
+ return {Status::NotOK, "invalid ziplist encoding"};
+ }
+ pos_ += len_bytes;
+ GET_OR_RET(peekOK(len));
+ value = input_.substr(pos_, len);
+ pos_ += len;
+ setPreEntryLen(len_bytes + len + prev_entry_encoded_size);
+ } else {
+ GET_OR_RET(peekOK(1));
+ pos_ += 1 /* the number bytes of length*/;
+ if ((encoding) == ZIP_INT_8B) {
+ GET_OR_RET(peekOK(1));
+ setPreEntryLen(2); // 1byte for encoding and 1byte for the prev entry
length
+ return std::to_string(input_[pos_++]);
+ } else if ((encoding) == ZIP_INT_16B) {
+ GET_OR_RET(peekOK(2));
+ int16_t i16 = 0;
+ memcpy(&i16, input_.data() + pos_, sizeof(int16_t));
+ memrev16ifbe(&i16);
+ setPreEntryLen(3); // 2byte for encoding and 1byte for the prev entry
length
+ pos_ += sizeof(int16_t);
+ return std::to_string(i16);
+ } else if ((encoding) == ZIP_INT_24B) {
+ GET_OR_RET(peekOK(3));
+ int32_t i32 = 0;
+ memcpy(reinterpret_cast<uint8_t*>(&i32) + 1, input_.data() + pos_,
sizeof(int32_t) - 1);
+ memrev32ifbe(&i32);
+ i32 >>= 8;
+ setPreEntryLen(4); // 3byte for encoding and 1byte for the prev entry
length
+ pos_ += sizeof(int32_t) - 1;
+ return std::to_string(i32);
+ } else if ((encoding) == ZIP_INT_32B) {
+ GET_OR_RET(peekOK(4));
+ int32_t i32 = 0;
+ memcpy(&i32, input_.data() + pos_, sizeof(int32_t));
+ memrev32ifbe(&i32);
+ setPreEntryLen(5); // 4byte for encoding and 1byte for the prev entry
length
+ pos_ += sizeof(int32_t);
+ return std::to_string(i32);
+ } else if ((encoding) == ZIP_INT_64B) {
+ GET_OR_RET(peekOK(8));
+ int64_t i64 = 0;
+ memcpy(&i64, input_.data() + pos_, sizeof(int64_t));
+ memrev64ifbe(&i64);
+ setPreEntryLen(9); // 8byte for encoding and 1byte for the prev entry
length
+ pos_ += sizeof(int64_t);
+ return std::to_string(i64);
+ } else if (encoding >= ZIP_INT_IMM_MIN && encoding <= ZIP_INT_IMM_MAX) {
+ setPreEntryLen(1); // 8byte for encoding and 1byte for the prev entry
length
+ return std::to_string((encoding & 0x0F) - 1);
+ } else {
+ return {Status::NotOK, "invalid ziplist encoding"};
+ }
+ }
+ return value;
+}
+
+StatusOr<std::vector<std::string>> ZipList::Entries() {
+ GET_OR_RET(peekOK(zlHeaderSize));
+ // ignore 8 bytes of total bytes and tail of zip list
+ auto zl_len = intrev16ifbe(*reinterpret_cast<const uint16_t*>(input_.data()
+ 8));
+ pos_ += zlHeaderSize;
+
+ std::vector<std::string> entries;
+ for (uint16_t i = 0; i < zl_len; i++) {
+ GET_OR_RET(peekOK(1));
+ if (static_cast<uint8_t>(input_[pos_]) == zlEnd) {
+ break;
+ }
+ auto entry = GET_OR_RET(Next());
+ entries.emplace_back(entry);
+ }
+ if (zl_len != entries.size()) {
+ return {Status::NotOK, "invalid ziplist length"};
+ }
+ return entries;
+}
+
+Status ZipList::peekOK(size_t n) {
+ if (pos_ + n > input_.size()) {
+ return {Status::NotOK, "reach the end of ziplist"};
+ }
+ return Status::OK();
+}
+
+uint32_t ZipList::getEncodedLengthSize(uint32_t len) { return len <
ZipListBigLen ? 1 : 5; }
diff --git a/src/storage/rdb_ziplist.h b/src/storage/rdb_ziplist.h
new file mode 100644
index 00000000..e9d05fde
--- /dev/null
+++ b/src/storage/rdb_ziplist.h
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#pragma once
+
+#include <map>
+#include <string_view>
+
+#include "common/status.h"
+
+class ZipList {
+ public:
+ explicit ZipList(std::string_view input) : input_(input){};
+ ~ZipList() = default;
+
+ StatusOr<std::string> Next();
+ StatusOr<std::vector<std::string>> Entries();
+
+ private:
+ std::string_view input_;
+ uint64_t pos_ = 0;
+ uint32_t pre_entry_len_ = 0;
+
+ Status peekOK(size_t n);
+ void setPreEntryLen(uint32_t len) { pre_entry_len_ = len; }
+ static uint32_t getEncodedLengthSize(uint32_t len);
+};
diff --git a/src/storage/rdb_zipmap.cc b/src/storage/rdb_zipmap.cc
new file mode 100644
index 00000000..724ce852
--- /dev/null
+++ b/src/storage/rdb_zipmap.cc
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "rdb_zipmap.h"
+
+#include "vendor/endianconv.h"
+
+// ZipMapBigLen is the largest length of a zip map that can be encoded with.
+// Need to traverse the entire zip map to get the length if it's over 254.
+const uint8_t ZipMapBigLen = 254;
+const uint8_t ZipMapEOF = 0xFF;
+
+Status ZipMap::peekOK(size_t n) {
+ if (pos_ + n > input_.size()) {
+ return {Status::NotOK, "reach the end of zipmap"};
+ }
+ return Status::OK();
+}
+
+uint32_t ZipMap::getEncodedLengthSize(uint32_t len) { return len <
ZipMapBigLen ? 1 : 5; }
+
+StatusOr<uint32_t> ZipMap::decodeLength() {
+ GET_OR_RET(peekOK(1));
+ unsigned int len = static_cast<uint8_t>(input_[pos_++]);
+ if (len == ZipMapBigLen) {
+ GET_OR_RET(peekOK(4));
+ memcpy(&len, input_.data() + pos_, sizeof(unsigned int));
+ memrev32ifbe(&len);
+ pos_ += 4;
+ }
+ return len;
+}
+
+StatusOr<std::pair<std::string, std::string>> ZipMap::Next() {
+ // The element of zip map is a key-value pair and each element is encoded as:
+ //
+ // <len>"key"<len><free>"value"
+ //
+ // if len = 254, then the real length is stored in the next 4 bytes
+ // if len = 255, then the zip map ends
+ // <free> is the number of free unused bytes after the value, it's always
1byte.
+ auto key_len = GET_OR_RET(decodeLength());
+ GET_OR_RET(peekOK(key_len));
+ auto key = input_.substr(pos_, key_len);
+ pos_ += key_len + getEncodedLengthSize(key_len);
+ auto val_len = GET_OR_RET(decodeLength());
+ GET_OR_RET(peekOK(val_len + 1 /* free byte */));
+ auto value = input_.substr(pos_, val_len);
+ pos_ += val_len + getEncodedLengthSize(val_len) + 1 /* free byte */;
+ return std::make_pair(key, value);
+}
+
+// Entries will parse all key-value pairs from the zip map binary format:
+//
+// <zmlen><len>"key"<len><free>"value"<len>"key"<len><free>"value"...
+//
+// <zmlen> is the number of zip map entries, and it's always 1byte length.
+// So you need to traverse the entire zip map to get the length if it's over
254.
+//
+// For more information, please infer:
https://github.com/redis/redis/blob/unstable/src/zipmap.c
+StatusOr<std::map<std::string, std::string>> ZipMap::Entries() {
+ std::map<std::string, std::string> kvs;
+ GET_OR_RET(peekOK(1));
+ auto zm_len = static_cast<uint8_t>(input_[pos_++]);
+
+ GET_OR_RET(peekOK(1));
+ while (static_cast<uint8_t>(input_[pos_]) != ZipMapEOF) {
+ auto kv = GET_OR_RET(Next());
+ kvs.insert(kv);
+ GET_OR_RET(peekOK(1));
+ }
+ if (zm_len < ZipMapBigLen && zm_len != kvs.size()) {
+ return {Status::NotOK, "invalid zipmap length"};
+ }
+ return kvs;
+}
diff --git a/src/storage/rdb_zipmap.h b/src/storage/rdb_zipmap.h
new file mode 100644
index 00000000..84fd0968
--- /dev/null
+++ b/src/storage/rdb_zipmap.h
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#pragma once
+
+#include <map>
+#include <string_view>
+
+#include "common/status.h"
+
+class ZipMap {
+ public:
+ explicit ZipMap(std::string_view input) : input_(input){};
+ ~ZipMap() = default;
+
+ StatusOr<std::pair<std::string, std::string>> Next();
+ StatusOr<std::map<std::string, std::string>> Entries();
+
+ private:
+ std::string_view input_;
+ uint64_t pos_ = 0;
+
+ Status peekOK(size_t n);
+ StatusOr<uint32_t> decodeLength();
+ static uint32_t getEncodedLengthSize(uint32_t len);
+};
diff --git a/src/vendor/crc64.cc b/src/vendor/crc64.cc
new file mode 100644
index 00000000..964292ce
--- /dev/null
+++ b/src/vendor/crc64.cc
@@ -0,0 +1,379 @@
+/* Copyright (c) 2014, Matt Stancliff <[email protected]>
+ * Copyright (c) 2020, Amazon Web Services
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE. */
+
+#include "crc64.h"
+// NOLINTBEGIN
+
+static uint64_t crc64_table[8][256] = {{0}};
+
+#define POLY UINT64_C(0xad93d23594c935a9)
+
+/* Fill in a CRC constants table. */
+void crcspeed64little_init(crcfn64 crcfn, uint64_t table[8][256]) {
+ uint64_t crc;
+
+ /* generate CRCs for all single byte sequences */
+ for (int n = 0; n < 256; n++) {
+ unsigned char v = n;
+ table[0][n] = crcfn(0, &v, 1);
+ }
+
+ /* generate nested CRC table for future slice-by-8 lookup */
+ for (int n = 0; n < 256; n++) {
+ crc = table[0][n];
+ for (int k = 1; k < 8; k++) {
+ crc = table[0][crc & 0xff] ^ (crc >> 8);
+ table[k][n] = crc;
+ }
+ }
+}
+
+void crcspeed16little_init(crcfn16 crcfn, uint16_t table[8][256]) {
+ uint16_t crc;
+
+ /* generate CRCs for all single byte sequences */
+ for (int n = 0; n < 256; n++) {
+ table[0][n] = crcfn(0, &n, 1);
+ }
+
+ /* generate nested CRC table for future slice-by-8 lookup */
+ for (int n = 0; n < 256; n++) {
+ crc = table[0][n];
+ for (int k = 1; k < 8; k++) {
+ crc = table[0][(crc >> 8) & 0xff] ^ (crc << 8);
+ table[k][n] = crc;
+ }
+ }
+}
+
+/* Reverse the bytes in a 64-bit word. */
+static inline uint64_t rev8(uint64_t a) {
+#if defined(__GNUC__) || defined(__clang__)
+ return __builtin_bswap64(a);
+#else
+ uint64_t m;
+
+ m = UINT64_C(0xff00ff00ff00ff);
+ a = ((a >> 8) & m) | (a & m) << 8;
+ m = UINT64_C(0xffff0000ffff);
+ a = ((a >> 16) & m) | (a & m) << 16;
+ return a >> 32 | a << 32;
+#endif
+}
+
+/* This function is called once to initialize the CRC table for use on a
+ big-endian architecture. */
+void crcspeed64big_init(crcfn64 fn, uint64_t big_table[8][256]) {
+ /* Create the little endian table then reverse all the entries. */
+ crcspeed64little_init(fn, big_table);
+ for (int k = 0; k < 8; k++) {
+ for (int n = 0; n < 256; n++) {
+ big_table[k][n] = rev8(big_table[k][n]);
+ }
+ }
+}
+
+void crcspeed16big_init(crcfn16 fn, uint16_t big_table[8][256]) {
+ /* Create the little endian table then reverse all the entries. */
+ crcspeed16little_init(fn, big_table);
+ for (int k = 0; k < 8; k++) {
+ for (int n = 0; n < 256; n++) {
+ big_table[k][n] = rev8(big_table[k][n]);
+ }
+ }
+}
+
+/* Calculate a non-inverted CRC multiple bytes at a time on a little-endian
+ * architecture. If you need inverted CRC, invert *before* calling and invert
+ * *after* calling.
+ * 64 bit crc = process 8 bytes at once;
+ */
+uint64_t crcspeed64little(uint64_t little_table[8][256], uint64_t crc, void
*buf, size_t len) {
+ unsigned char *next = static_cast<unsigned char *>(buf);
+
+ /* process individual bytes until we reach an 8-byte aligned pointer */
+ while (len && ((uintptr_t)next & 7) != 0) {
+ crc = little_table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8);
+ len--;
+ }
+
+ /* fast middle processing, 8 bytes (aligned!) per loop */
+ while (len >= 8) {
+ crc ^= *(uint64_t *)next;
+ crc = little_table[7][crc & 0xff] ^ little_table[6][(crc >> 8) & 0xff] ^
little_table[5][(crc >> 16) & 0xff] ^
+ little_table[4][(crc >> 24) & 0xff] ^ little_table[3][(crc >> 32) &
0xff] ^
+ little_table[2][(crc >> 40) & 0xff] ^ little_table[1][(crc >> 48) &
0xff] ^ little_table[0][crc >> 56];
+ next += 8;
+ len -= 8;
+ }
+
+ /* process remaining bytes (can't be larger than 8) */
+ while (len) {
+ crc = little_table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8);
+ len--;
+ }
+
+ return crc;
+}
+
+uint16_t crcspeed16little(uint16_t little_table[8][256], uint16_t crc, void
*buf, size_t len) {
+ unsigned char *next = static_cast<unsigned char *>(buf);
+
+ /* process individual bytes until we reach an 8-byte aligned pointer */
+ while (len && ((uintptr_t)next & 7) != 0) {
+ crc = little_table[0][((crc >> 8) ^ *next++) & 0xff] ^ (crc << 8);
+ len--;
+ }
+
+ /* fast middle processing, 8 bytes (aligned!) per loop */
+ while (len >= 8) {
+ uint64_t n = *(uint64_t *)next;
+ crc = little_table[7][(n & 0xff) ^ ((crc >> 8) & 0xff)] ^
little_table[6][((n >> 8) & 0xff) ^ (crc & 0xff)] ^
+ little_table[5][(n >> 16) & 0xff] ^ little_table[4][(n >> 24) &
0xff] ^ little_table[3][(n >> 32) & 0xff] ^
+ little_table[2][(n >> 40) & 0xff] ^ little_table[1][(n >> 48) &
0xff] ^ little_table[0][n >> 56];
+ next += 8;
+ len -= 8;
+ }
+
+ /* process remaining bytes (can't be larger than 8) */
+ while (len) {
+ crc = little_table[0][((crc >> 8) ^ *next++) & 0xff] ^ (crc << 8);
+ len--;
+ }
+
+ return crc;
+}
+
+/* Calculate a non-inverted CRC eight bytes at a time on a big-endian
+ * architecture.
+ */
+uint64_t crcspeed64big(uint64_t big_table[8][256], uint64_t crc, void *buf,
size_t len) {
+ unsigned char *next = static_cast<unsigned char *>(buf);
+
+ crc = rev8(crc);
+ while (len && ((uintptr_t)next & 7) != 0) {
+ crc = big_table[0][(crc >> 56) ^ *next++] ^ (crc << 8);
+ len--;
+ }
+
+ while (len >= 8) {
+ crc ^= *(uint64_t *)next;
+ crc = big_table[0][crc & 0xff] ^ big_table[1][(crc >> 8) & 0xff] ^
big_table[2][(crc >> 16) & 0xff] ^
+ big_table[3][(crc >> 24) & 0xff] ^ big_table[4][(crc >> 32) & 0xff]
^ big_table[5][(crc >> 40) & 0xff] ^
+ big_table[6][(crc >> 48) & 0xff] ^ big_table[7][crc >> 56];
+ next += 8;
+ len -= 8;
+ }
+
+ while (len) {
+ crc = big_table[0][(crc >> 56) ^ *next++] ^ (crc << 8);
+ len--;
+ }
+
+ return rev8(crc);
+}
+
+/* WARNING: Completely untested on big endian architecture. Possibly broken.
*/
+uint16_t crcspeed16big(uint16_t big_table[8][256], uint16_t crc_in, void *buf,
size_t len) {
+ unsigned char *next = static_cast<unsigned char *>(buf);
+ uint64_t crc = crc_in;
+
+ crc = rev8(crc);
+ while (len && ((uintptr_t)next & 7) != 0) {
+ crc = big_table[0][((crc >> (56 - 8)) ^ *next++) & 0xff] ^ (crc >> 8);
+ len--;
+ }
+
+ while (len >= 8) {
+ uint64_t n = *(uint64_t *)next;
+ crc = big_table[0][(n & 0xff) ^ ((crc >> (56 - 8)) & 0xff)] ^
big_table[1][((n >> 8) & 0xff) ^ (crc & 0xff)] ^
+ big_table[2][(n >> 16) & 0xff] ^ big_table[3][(n >> 24) & 0xff] ^
big_table[4][(n >> 32) & 0xff] ^
+ big_table[5][(n >> 40) & 0xff] ^ big_table[6][(n >> 48) & 0xff] ^
big_table[7][n >> 56];
+ next += 8;
+ len -= 8;
+ }
+
+ while (len) {
+ crc = big_table[0][((crc >> (56 - 8)) ^ *next++) & 0xff] ^ (crc >> 8);
+ len--;
+ }
+
+ return rev8(crc);
+}
+
+/* Return the CRC of buf[0..len-1] with initial crc, processing eight bytes
+ at a time using passed-in lookup table.
+ This selects one of two routines depending on the endianness of
+ the architecture. */
+uint64_t crcspeed64native(uint64_t table[8][256], uint64_t crc, void *buf,
size_t len) {
+ uint64_t n = 1;
+
+ return *(char *)&n ? crcspeed64little(table, crc, buf, len) :
crcspeed64big(table, crc, buf, len);
+}
+
+uint16_t crcspeed16native(uint16_t table[8][256], uint16_t crc, void *buf,
size_t len) {
+ uint64_t n = 1;
+
+ return *(char *)&n ? crcspeed16little(table, crc, buf, len) :
crcspeed16big(table, crc, buf, len);
+}
+
+/* Initialize CRC lookup table in architecture-dependent manner. */
+void crcspeed64native_init(crcfn64 fn, uint64_t table[8][256]) {
+ uint64_t n = 1;
+
+ *(char *)&n ? crcspeed64little_init(fn, table) : crcspeed64big_init(fn,
table);
+}
+
+void crcspeed16native_init(crcfn16 fn, uint16_t table[8][256]) {
+ uint64_t n = 1;
+
+ *(char *)&n ? crcspeed16little_init(fn, table) : crcspeed16big_init(fn,
table);
+}
+
+/******************** BEGIN GENERATED PYCRC FUNCTIONS ********************/
+/**
+ * Generated on Sun Dec 21 14:14:07 2014,
+ * by pycrc v0.8.2, https://www.tty1.net/pycrc/
+ *
+ * LICENSE ON GENERATED CODE:
+ * ==========================
+ * As of version 0.6, pycrc is released under the terms of the MIT licence.
+ * The code generated by pycrc is not considered a substantial portion of the
+ * software, therefore the author of pycrc will not claim any copyright on
+ * the generated code.
+ * ==========================
+ *
+ * CRC configuration:
+ * Width = 64
+ * Poly = 0xad93d23594c935a9
+ * XorIn = 0xffffffffffffffff
+ * ReflectIn = True
+ * XorOut = 0x0000000000000000
+ * ReflectOut = True
+ * Algorithm = bit-by-bit-fast
+ *
+ * Modifications after generation (by matt):
+ * - included finalize step in-line with update for single-call generation
+ * - re-worked some inner variable architectures
+ * - adjusted function parameters to match expected prototypes.
+ *****************************************************************************/
+
+/**
+ * Reflect all bits of a \a data word of \a data_len bytes.
+ *
+ * \param data The data word to be reflected.
+ * \param data_len The width of \a data expressed in number of bits.
+ * \return The reflected data.
+ *****************************************************************************/
+static inline uint_fast64_t crc_reflect(uint_fast64_t data, size_t data_len) {
+ uint_fast64_t ret = data & 0x01;
+
+ for (size_t i = 1; i < data_len; i++) {
+ data >>= 1;
+ ret = (ret << 1) | (data & 0x01);
+ }
+
+ return ret;
+}
+
+/**
+ * Update the crc value with new data.
+ *
+ * \param crc The current crc value.
+ * \param data Pointer to a buffer of \a data_len bytes.
+ * \param data_len Number of bytes in the \a data buffer.
+ * \return The updated crc value.
+
******************************************************************************/
+uint64_t _crc64(uint_fast64_t crc, const void *in_data, const uint64_t len) {
+ const uint8_t *data = static_cast<const uint8_t *>(in_data);
+ unsigned long long bit;
+
+ for (uint64_t offset = 0; offset < len; offset++) {
+ uint8_t c = data[offset];
+ for (uint_fast8_t i = 0x01; i & 0xff; i <<= 1) {
+ bit = crc & 0x8000000000000000;
+ if (c & i) {
+ bit = !bit;
+ }
+
+ crc <<= 1;
+ if (bit) {
+ crc ^= POLY;
+ }
+ }
+
+ crc &= 0xffffffffffffffff;
+ }
+
+ crc = crc & 0xffffffffffffffff;
+ return crc_reflect(crc, 64) ^ 0x0000000000000000;
+}
+
+/******************** END GENERATED PYCRC FUNCTIONS ********************/
+
+/* Initializes the 16KB lookup tables. */
+void crc64_init(void) { crcspeed64native_init(_crc64, crc64_table); }
+
+/* Compute crc64 */
+uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) {
+ return crcspeed64native(crc64_table, crc, (void *)s, l);
+}
+
+/* Test main */
+#ifdef REDIS_TEST
+#include <stdio.h>
+
+#define UNUSED(x) (void)(x)
+int crc64Test(int argc, char *argv[], int flags) {
+ UNUSED(argc);
+ UNUSED(argv);
+ UNUSED(flags);
+ crc64_init();
+ printf("[calcula]: e9c6d914c4b8d9ca == %016" PRIx64 "\n",
(uint64_t)_crc64(0, "123456789", 9));
+ printf("[64speed]: e9c6d914c4b8d9ca == %016" PRIx64 "\n", (uint64_t)crc64(0,
(unsigned char *)"123456789", 9));
+ char li[] =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed "
+ "do eiusmod tempor incididunt ut labore et dolore magna "
+ "aliqua. Ut enim ad minim veniam, quis nostrud exercitation "
+ "ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis "
+ "aute irure dolor in reprehenderit in voluptate velit esse "
+ "cillum dolore eu fugiat nulla pariatur. Excepteur sint "
+ "occaecat cupidatat non proident, sunt in culpa qui officia "
+ "deserunt mollit anim id est laborum.";
+ printf("[calcula]: c7794709e69683b3 == %016" PRIx64 "\n",
(uint64_t)_crc64(0, li, sizeof(li)));
+ printf("[64speed]: c7794709e69683b3 == %016" PRIx64 "\n", (uint64_t)crc64(0,
(unsigned char *)li, sizeof(li)));
+ return 0;
+}
+
+#endif
+
+#ifdef REDIS_TEST_MAIN
+int main(int argc, char *argv[]) { return crc64Test(argc, argv); }
+
+#endif
+// NOLINTEND
diff --git a/src/vendor/crc64.h b/src/vendor/crc64.h
new file mode 100644
index 00000000..4d8f310f
--- /dev/null
+++ b/src/vendor/crc64.h
@@ -0,0 +1,57 @@
+/* Copyright (c) 2014, Matt Stancliff <[email protected]>
+ * Copyright (c) 2020, Amazon Web Services
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE. */
+
+#pragma once
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+// NOLINTBEGIN
+void crc64_init(void);
+uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
+
+typedef uint64_t (*crcfn64)(uint64_t, const void *, const uint64_t);
+typedef uint16_t (*crcfn16)(uint16_t, const void *, const uint64_t);
+
+/* CRC-64 */
+void crcspeed64little_init(crcfn64 fn, uint64_t table[8][256]);
+void crcspeed64big_init(crcfn64 fn, uint64_t table[8][256]);
+void crcspeed64native_init(crcfn64 fn, uint64_t table[8][256]);
+
+uint64_t crcspeed64little(uint64_t table[8][256], uint64_t crc, void *buf,
size_t len);
+uint64_t crcspeed64big(uint64_t table[8][256], uint64_t crc, void *buf, size_t
len);
+uint64_t crcspeed64native(uint64_t table[8][256], uint64_t crc, void *buf,
size_t len);
+
+/* CRC-16 */
+void crcspeed16little_init(crcfn16 fn, uint16_t table[8][256]);
+void crcspeed16big_init(crcfn16 fn, uint16_t table[8][256]);
+void crcspeed16native_init(crcfn16 fn, uint16_t table[8][256]);
+
+uint16_t crcspeed16little(uint16_t table[8][256], uint16_t crc, void *buf,
size_t len);
+uint16_t crcspeed16big(uint16_t table[8][256], uint16_t crc, void *buf, size_t
len);
+uint16_t crcspeed16native(uint16_t table[8][256], uint16_t crc, void *buf,
size_t len);
+// NOLINTEND
diff --git a/src/vendor/endianconv.cc b/src/vendor/endianconv.cc
new file mode 100644
index 00000000..4ebbd1eb
--- /dev/null
+++ b/src/vendor/endianconv.cc
@@ -0,0 +1,90 @@
+/* Copyright (c) 2014, Matt Stancliff <[email protected]>
+ * Copyright (c) 2020, Amazon Web Services
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE. */
+
+#include "endianconv.h"
+
+// NOLINTBEGIN
+
+/* Toggle the 16 bit unsigned integer pointed by *p from little endian to
+ * big endian */
+void memrev16(void *p) {
+ unsigned char *x = static_cast<unsigned char *>(p), t;
+
+ t = x[0];
+ x[0] = x[1];
+ x[1] = t;
+}
+
+/* Toggle the 32 bit unsigned integer pointed by *p from little endian to
+ * big endian */
+void memrev32(void *p) {
+ unsigned char *x = static_cast<unsigned char *>(p), t;
+
+ t = x[0];
+ x[0] = x[3];
+ x[3] = t;
+ t = x[1];
+ x[1] = x[2];
+ x[2] = t;
+}
+
+/* Toggle the 64 bit unsigned integer pointed by *p from little endian to
+ * big endian */
+void memrev64(void *p) {
+ unsigned char *x = static_cast<unsigned char *>(p), t;
+
+ t = x[0];
+ x[0] = x[7];
+ x[7] = t;
+ t = x[1];
+ x[1] = x[6];
+ x[6] = t;
+ t = x[2];
+ x[2] = x[5];
+ x[5] = t;
+ t = x[3];
+ x[3] = x[4];
+ x[4] = t;
+}
+
+uint16_t intrev16(uint16_t v) {
+ memrev16(&v);
+ return v;
+}
+
+uint32_t intrev32(uint32_t v) {
+ memrev32(&v);
+ return v;
+}
+
+uint64_t intrev64(uint64_t v) {
+ memrev64(&v);
+ return v;
+}
+
+// NOLINTEND
diff --git a/src/vendor/endianconv.h b/src/vendor/endianconv.h
new file mode 100644
index 00000000..e2704275
--- /dev/null
+++ b/src/vendor/endianconv.h
@@ -0,0 +1,62 @@
+/* Copyright (c) 2014, Matt Stancliff <[email protected]>
+ * Copyright (c) 2020, Amazon Web Services
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE. */
+
+#pragma once
+
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+
+// NOLINTBEGIN
+
+void memrev16(void *p);
+void memrev32(void *p);
+void memrev64(void *p);
+uint16_t intrev16(uint16_t v);
+uint32_t intrev32(uint32_t v);
+uint64_t intrev64(uint64_t v);
+
+/* variants of the function doing the actual conversion only if the target
+ * host is big endian */
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+#define memrev16ifbe(p) ((void)(0))
+#define memrev32ifbe(p) ((void)(0))
+#define memrev64ifbe(p) ((void)(0))
+#define intrev16ifbe(v) (v)
+#define intrev32ifbe(v) (v)
+#define intrev64ifbe(v) (v)
+#else
+#define memrev16ifbe(p) memrev16(p)
+#define memrev32ifbe(p) memrev32(p)
+#define memrev64ifbe(p) memrev64(p)
+#define intrev16ifbe(v) intrev16(v)
+#define intrev32ifbe(v) intrev32(v)
+#define intrev64ifbe(v) intrev64(v)
+#endif
+
+// NOLINTEND
diff --git a/src/vendor/lzf.cc b/src/vendor/lzf.cc
new file mode 100644
index 00000000..b7c7a4c4
--- /dev/null
+++ b/src/vendor/lzf.cc
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2000-2010 Marc Alexander Lehmann <[email protected]>
+ *
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License ("GPL") version 2 or any later version,
+ * in which case the provisions of the GPL are applicable instead of
+ * the above. If you wish to allow the use of your version of this file
+ * only under the terms of the GPL and not to allow others to use your
+ * version of this file under the BSD license, indicate your decision
+ * by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete the
+ * provisions above, a recipient may use your version of this file under
+ * either the BSD or the GPL.
+ */
+
+#include "lzf.h"
+
+// NOLINTBEGIN
+#if AVOID_ERRNO
+#define SET_ERRNO(n)
+#else
+#include <errno.h>
+#define SET_ERRNO(n) errno = (n)
+#endif
+
+#if USE_REP_MOVSB /* small win on amd, big loss on intel */
+#if (__i386 || __amd64) && __GNUC__ >= 3
+#define lzf_movsb(dst, src, len) asm("rep movsb" : "=D"(dst), "=S"(src),
"=c"(len) : "0"(dst), "1"(src), "2"(len));
+#endif
+#endif
+
+#if defined(__GNUC__) && __GNUC__ >= 7
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
+#endif
+size_t lzf_decompress(const void *const in_data, size_t in_len, void
*out_data, size_t out_len) {
+ u8 const *ip = (const u8 *)in_data;
+ u8 *op = (u8 *)out_data;
+ u8 const *const in_end = ip + in_len;
+ u8 *const out_end = op + out_len;
+
+ while (ip < in_end) {
+ unsigned int ctrl;
+ ctrl = *ip++;
+
+ if (ctrl < (1 << 5)) /* literal run */
+ {
+ ctrl++;
+
+ if (op + ctrl > out_end) {
+ SET_ERRNO(E2BIG);
+ return 0;
+ }
+
+#if CHECK_INPUT
+ if (ip + ctrl > in_end) {
+ SET_ERRNO(EINVAL);
+ return 0;
+ }
+#endif
+#ifdef lzf_movsb
+ lzf_movsb(op, ip, ctrl);
+#else
+ switch (ctrl) {
+ case 32:
+ *op++ = *ip++;
+ case 31:
+ *op++ = *ip++;
+ case 30:
+ *op++ = *ip++;
+ case 29:
+ *op++ = *ip++;
+ case 28:
+ *op++ = *ip++;
+ case 27:
+ *op++ = *ip++;
+ case 26:
+ *op++ = *ip++;
+ case 25:
+ *op++ = *ip++;
+ case 24:
+ *op++ = *ip++;
+ case 23:
+ *op++ = *ip++;
+ case 22:
+ *op++ = *ip++;
+ case 21:
+ *op++ = *ip++;
+ case 20:
+ *op++ = *ip++;
+ case 19:
+ *op++ = *ip++;
+ case 18:
+ *op++ = *ip++;
+ case 17:
+ *op++ = *ip++;
+ case 16:
+ *op++ = *ip++;
+ case 15:
+ *op++ = *ip++;
+ case 14:
+ *op++ = *ip++;
+ case 13:
+ *op++ = *ip++;
+ case 12:
+ *op++ = *ip++;
+ case 11:
+ *op++ = *ip++;
+ case 10:
+ *op++ = *ip++;
+ case 9:
+ *op++ = *ip++;
+ case 8:
+ *op++ = *ip++;
+ case 7:
+ *op++ = *ip++;
+ case 6:
+ *op++ = *ip++;
+ case 5:
+ *op++ = *ip++;
+ case 4:
+ *op++ = *ip++;
+ case 3:
+ *op++ = *ip++;
+ case 2:
+ *op++ = *ip++;
+ case 1:
+ *op++ = *ip++;
+ }
+#endif
+ } else /* back reference */
+ {
+ unsigned int len = ctrl >> 5;
+
+ u8 *ref = op - ((ctrl & 0x1f) << 8) - 1;
+
+#if CHECK_INPUT
+ if (ip >= in_end) {
+ SET_ERRNO(EINVAL);
+ return 0;
+ }
+#endif
+ if (len == 7) {
+ len += *ip++;
+#if CHECK_INPUT
+ if (ip >= in_end) {
+ SET_ERRNO(EINVAL);
+ return 0;
+ }
+#endif
+ }
+
+ ref -= *ip++;
+
+ if (op + len + 2 > out_end) {
+ SET_ERRNO(E2BIG);
+ return 0;
+ }
+
+ if (ref < (u8 *)out_data) {
+ SET_ERRNO(EINVAL);
+ return 0;
+ }
+
+#ifdef lzf_movsb
+ len += 2;
+ lzf_movsb(op, ref, len);
+#else
+ switch (len) {
+ default:
+ len += 2;
+
+ if (op >= ref + len) {
+ /* disjunct areas */
+ memcpy(op, ref, len);
+ op += len;
+ } else {
+ /* overlapping, use octte by octte copying */
+ do *op++ = *ref++;
+ while (--len);
+ }
+
+ break;
+
+ case 9:
+ *op++ = *ref++; /* fall-thru */
+ case 8:
+ *op++ = *ref++; /* fall-thru */
+ case 7:
+ *op++ = *ref++; /* fall-thru */
+ case 6:
+ *op++ = *ref++; /* fall-thru */
+ case 5:
+ *op++ = *ref++; /* fall-thru */
+ case 4:
+ *op++ = *ref++; /* fall-thru */
+ case 3:
+ *op++ = *ref++; /* fall-thru */
+ case 2:
+ *op++ = *ref++; /* fall-thru */
+ case 1:
+ *op++ = *ref++; /* fall-thru */
+ case 0:
+ *op++ = *ref++; /* two octets more */
+ *op++ = *ref++; /* fall-thru */
+ }
+#endif
+ }
+ }
+
+ return op - (u8 *)out_data;
+}
+#if defined(__GNUC__) && __GNUC__ >= 5
+#pragma GCC diagnostic pop
+#endif
+// NOLINTEND
diff --git a/src/vendor/lzf.h b/src/vendor/lzf.h
new file mode 100644
index 00000000..6f849915
--- /dev/null
+++ b/src/vendor/lzf.h
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2000-2007 Marc Alexander Lehmann <[email protected]>
+ *
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License ("GPL") version 2 or any later version,
+ * in which case the provisions of the GPL are applicable instead of
+ * the above. If you wish to allow the use of your version of this file
+ * only under the terms of the GPL and not to allow others to use your
+ * version of this file under the BSD license, indicate your decision
+ * by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL. If you do not delete the
+ * provisions above, a recipient may use your version of this file under
+ * either the BSD or the GPL.
+ */
+
+#pragma once
+
+#include <stddef.h>
+// NOLINTBEGIN
+
+/*
+ * Decompress data compressed with some version of the lzf_compress
+ * function and stored at location in_data and length in_len. The result
+ * will be stored at out_data up to a maximum of out_len characters.
+ *
+ * If the output buffer is not large enough to hold the decompressed
+ * data, a 0 is returned and errno is set to E2BIG. Otherwise the number
+ * of decompressed bytes (i.e. the original length of the data) is
+ * returned.
+ *
+ * If an error in the compressed data is detected, a zero is returned and
+ * errno is set to EINVAL.
+ *
+ * This function is very fast, about as fast as a copying loop.
+ */
+size_t lzf_decompress(const void *const in_data, size_t in_len, void
*out_data, size_t out_len);
+
+/*
+ * Size of hashtable is (1 << HLOG) * sizeof (char *)
+ * decompression is independent of the hash table size
+ * the difference between 15 and 14 is very small
+ * for small blocks (and 14 is usually a bit faster).
+ * For a low-memory/faster configuration, use HLOG == 13;
+ * For best compression, use 15 or 16 (or more, up to 22).
+ */
+#ifndef HLOG
+#define HLOG 16
+#endif
+
+/*
+ * Sacrifice very little compression quality in favour of compression speed.
+ * This gives almost the same compression as the default code, and is
+ * (very roughly) 15% faster. This is the preferred mode of operation.
+ */
+#ifndef VERY_FAST
+#define VERY_FAST 1
+#endif
+
+/*
+ * Sacrifice some more compression quality in favour of compression speed.
+ * (roughly 1-2% worse compression for large blocks and
+ * 9-10% for small, redundant, blocks and >>20% better speed in both cases)
+ * In short: when in need for speed, enable this for binary data,
+ * possibly disable this for text data.
+ */
+#ifndef ULTRA_FAST
+#define ULTRA_FAST 0
+#endif
+
+/*
+ * Unconditionally aligning does not cost very much, so do it if unsure
+ */
+#ifndef STRICT_ALIGN
+#if !(defined(__i386) || defined(__amd64))
+#define STRICT_ALIGN 1
+#else
+#define STRICT_ALIGN 0
+#endif
+#endif
+
+/*
+ * You may choose to pre-set the hash table (might be faster on some
+ * modern cpus and large (>>64k) blocks, and also makes compression
+ * deterministic/repeatable when the configuration otherwise is the same).
+ */
+#ifndef INIT_HTAB
+#define INIT_HTAB 0
+#endif
+
+/*
+ * Avoid assigning values to errno variable? for some embedding purposes
+ * (linux kernel for example), this is necessary. NOTE: this breaks
+ * the documentation in lzf.h. Avoiding errno has no speed impact.
+ */
+#ifndef AVOID_ERRNO
+#define AVOID_ERRNO 0
+#endif
+
+/*
+ * Whether to pass the LZF_STATE variable as argument, or allocate it
+ * on the stack. For small-stack environments, define this to 1.
+ * NOTE: this breaks the prototype in lzf.h.
+ */
+#ifndef LZF_STATE_ARG
+#define LZF_STATE_ARG 0
+#endif
+
+/*
+ * Whether to add extra checks for input validity in lzf_decompress
+ * and return EINVAL if the input stream has been corrupted. This
+ * only shields against overflowing the input buffer and will not
+ * detect most corrupted streams.
+ * This check is not normally noticeable on modern hardware
+ * (<1% slowdown), but might slow down older cpus considerably.
+ */
+#ifndef CHECK_INPUT
+#define CHECK_INPUT 1
+#endif
+
+/*
+ * Whether to store pointers or offsets inside the hash table. On
+ * 64 bit architectures, pointers take up twice as much space,
+ * and might also be slower. Default is to autodetect.
+ * Notice: Don't set this value to 1, it will result in 'LZF_HSLOT'
+ * not being able to store offset above UINT32_MAX in 64bit. */
+#define LZF_USE_OFFSETS 0
+
+/*****************************************************************************/
+/* nothing should be changed below */
+
+#ifdef __cplusplus
+#include <climits>
+#include <cstring>
+#else
+#include <limits.h>
+#include <string.h>
+#endif
+
+#ifndef LZF_USE_OFFSETS
+#if defined(WIN32)
+#define LZF_USE_OFFSETS defined(_M_X64)
+#else
+#if __cplusplus > 199711L
+#include <cstdint>
+#else
+#include <stdint.h>
+#endif
+#define LZF_USE_OFFSETS (UINTPTR_MAX > 0xffffffffU)
+#endif
+#endif
+
+typedef unsigned char u8;
+
+#if LZF_USE_OFFSETS
+#define LZF_HSLOT_BIAS ((const u8 *)in_data)
+typedef unsigned int LZF_HSLOT;
+#else
+#define LZF_HSLOT_BIAS 0
+typedef const u8 *LZF_HSLOT;
+#endif
+
+typedef LZF_HSLOT LZF_STATE[1 << (HLOG)];
+
+#if !STRICT_ALIGN
+/* for unaligned accesses we need a 16 bit datatype. */
+#if USHRT_MAX == 65535
+typedef unsigned short u16;
+#elif UINT_MAX == 65535
+typedef unsigned int u16;
+#else
+#undef STRICT_ALIGN
+#define STRICT_ALIGN 1
+#endif
+#endif
+
+#if ULTRA_FAST
+#undef VERY_FAST
+#endif
+// NOLINTEND
diff --git a/tests/gocase/unit/restore/restore_test.go
b/tests/gocase/unit/restore/restore_test.go
new file mode 100644
index 00000000..0fa8b11a
--- /dev/null
+++ b/tests/gocase/unit/restore/restore_test.go
@@ -0,0 +1,278 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package restore
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ "github.com/apache/kvrocks/tests/gocase/util"
+ "github.com/redis/go-redis/v9"
+ "github.com/stretchr/testify/require"
+)
+
+func TestRestore_String(t *testing.T) {
+ srv := util.StartServer(t, map[string]string{})
+ defer srv.Close()
+
+ ctx := context.Background()
+ rdb := srv.NewClient()
+ defer func() { require.NoError(t, rdb.Close()) }()
+
+ key := util.RandString(32, 64, util.Alpha)
+ value := "\x00\x03bar\n\x00\xe6\xbeI`\xeef\xfd\x17"
+ require.NoError(t, rdb.Restore(ctx, key, 0, value).Err())
+ require.Equal(t, "bar", rdb.Get(ctx, key).Val())
+ require.EqualValues(t, -1, rdb.TTL(ctx, key).Val())
+
+ // Cannot restore to an existing key.
+ newValue := "\x00\x03new\n\x000tA\x15\x9ch\x17|"
+ require.EqualError(t, rdb.Restore(ctx, key, 0, newValue).Err(),
+ "ERR target key name already exists.")
+
+ // Restore exists key with the replacement flag
+ require.NoError(t, rdb.RestoreReplace(ctx, key, 10*time.Second,
newValue).Err())
+ require.Equal(t, "new", rdb.Get(ctx, key).Val())
+ require.Greater(t, rdb.TTL(ctx, key).Val(), 5*time.Second)
+ require.LessOrEqual(t, rdb.TTL(ctx, key).Val(), 10*time.Second)
+}
+
+func TestRestore_Hash(t *testing.T) {
+ srv := util.StartServer(t, map[string]string{})
+ defer srv.Close()
+
+ ctx := context.Background()
+ rdb := srv.NewClient()
+ defer func() { require.NoError(t, rdb.Close()) }()
+
+ t.Run("Hash object encoding", func(t *testing.T) {
+ key := util.RandString(32, 64, util.Alpha)
+ value :=
"\x04\x06\x02f2\xc0\x01\x02f3\xc1\xd2\x04\xc3\x12@F\n01234567890\xe00\t\x0189\xc0\x01\x02f4\xc2Na\xbc\x00\x02f6\xc0b\x02f5\x0e12345678901234\a\x00\xc5f\xe3\xf8\xa4w\a)"
+ require.NoError(t, rdb.Restore(ctx, key, 0, value).Err())
+ require.EqualValues(t, map[string]string{
+
"0123456789012345678901234567890123456789012345678901234567890123456789": "1",
+ "f2": "1",
+ "f3": "1234",
+ "f4": "12345678",
+ "f5": "12345678901234",
+ "f6": "98",
+ }, rdb.HGetAll(ctx, key).Val())
+ })
+
+ t.Run("Zip list encoding", func(t *testing.T) {
+ key := util.RandString(32, 64, util.Alpha)
+ value :=
"\r22\x00\x00\x00,\x00\x00\x00\b\x00\x00\x0bxxxxxxxxxxy\r\xf5\x02\x02f2\x04\xfe
\x03\x02f3\x04\xc0,\x01\x04\x02f4\x04\xf0\x87\xd6\x12\xff\a\x00\xba\x1dc/U\xa5\x88\x94"
+ require.NoError(t, rdb.Restore(ctx, key, 0, value).Err())
+ require.EqualValues(t, map[string]string{
+ "xxxxxxxxxxy": "4",
+ "f2": "32",
+ "f3": "300",
+ "f4": "1234567",
+ }, rdb.HGetAll(ctx, key).Val())
+ })
+
+ t.Run("List pack encoding", func(t *testing.T) {
+ key := util.RandString(32, 64, util.Alpha)
+ value :=
"\x10\x1f\x1f\x00\x00\x00\x06\x00\x82f1\x03\x82v1\x03\x82f2\x03\x82v2\x03\x82f3\x03\x82v3\x03\xff\x0b\x00L\xcd\xdfe(4xd"
+ require.NoError(t, rdb.Restore(ctx, key, 0, value).Err())
+ require.EqualValues(t, map[string]string{
+ "f1": "v1",
+ "f2": "v2",
+ "f3": "v3",
+ }, rdb.HGetAll(ctx, key).Val())
+ })
+}
+
+func TestRestore_ZSet(t *testing.T) {
+ srv := util.StartServer(t, map[string]string{})
+ defer srv.Close()
+
+ ctx := context.Background()
+ rdb := srv.NewClient()
+ defer func() { require.NoError(t, rdb.Close()) }()
+
+ t.Run("ZSet object encoding", func(t *testing.T) {
+ key := util.RandString(32, 64, util.Alpha)
+ value :=
"\x03\x03\x04yyyy\x12456789.20000000001\x15012345678901234567890\x05123.5\x04xxxx\x011\x06\x00\xb3\xa7b%\x96\x01\xe8\xdb"
+ require.NoError(t, rdb.Restore(ctx, key, 0, value).Err())
+ require.EqualValues(t, []redis.Z{
+ {Member: "xxxx", Score: 1.0},
+ {Member: "012345678901234567890", Score: 123.5},
+ {Member: "yyyy", Score: 456789.2},
+ }, rdb.ZRangeWithScores(ctx, key, 0, -1).Val())
+ })
+
+ t.Run("List pack encoding", func(t *testing.T) {
+ key := util.RandString(32, 64, util.Alpha)
+ value :=
"\x11''\x00\x00\x00\x06\x00\x81a\x02\x831.2\x04\x81b\x02\x861234.5\a\x81c\x02\xf4\xd8Y`\xe0\x02\x00\x00\x00\t\xff\n\x00\xce\xfdp\xbdHN\xdbG"
+ require.NoError(t, rdb.Restore(ctx, key, 0, value).Err())
+ require.EqualValues(t, []redis.Z{
+ {Member: "a", Score: 1.2},
+ {Member: "b", Score: 1234.5},
+ {Member: "c", Score: 12354345432},
+ }, rdb.ZRangeWithScores(ctx, key, 0, -1).Val())
+ })
+
+ t.Run("Zip list encoding", func(t *testing.T) {
+ key := util.RandString(32, 64, util.Alpha)
+ value :=
"\x0c22\x00\x00\x00,\x00\x00\x00\x06\x00\x00\x06zzzzzz\b\x031.5\x05\x04xxxx\x06\x061234.5\b\x05yyyyy\a\xf0@\xe2\x01\xff\x06\x00)\xe2\xb6\x8b\x9b9\xc1&"
+ require.NoError(t, rdb.Restore(ctx, key, 0, value).Err())
+ require.EqualValues(t, []redis.Z{
+ {Member: "zzzzzz", Score: 1.5},
+ {Member: "xxxx", Score: 1234.5},
+ {Member: "yyyyy", Score: 123456},
+ }, rdb.ZRangeWithScores(ctx, key, 0, -1).Val())
+ })
+}
+
+func TestRestore_List(t *testing.T) {
+ srv := util.StartServer(t, map[string]string{})
+ defer srv.Close()
+
+ ctx := context.Background()
+ rdb := srv.NewClient()
+ defer func() { require.NoError(t, rdb.Close()) }()
+
+ t.Run("List object encoding", func(t *testing.T) {
+ key := util.RandString(32, 64, util.Alpha)
+ value :=
"\x01\x05\x04hijk\x03efg\x02bc\x01a\x15012345678901234567890\x06\x00\xcb\xdc\xf9\x0ee|{g"
+ require.NoError(t, rdb.Restore(ctx, key, 0, value).Err())
+ require.EqualValues(t, []string{
+ "hijk", "efg", "bc", "a", "012345678901234567890",
+ }, rdb.LRange(ctx, key, 0, -1).Val())
+ })
+
+ t.Run("Zip list encoding", func(t *testing.T) {
+ key := util.RandString(32, 64, util.Alpha)
+ value :=
"\n##\x00\x00\x00\x1f\x00\x00\x00\b\x00\x00\xc090\x04\xc0\xd2\x04\x04\xfe{\x03\xfd\x02\xf2\x02\x01c\x03\x01b\x03\x01a\xff\x06\x00(H.j/\xf9\x04\x8f"
+ require.NoError(t, rdb.Restore(ctx, key, 0, value).Err())
+ require.EqualValues(t, []string{
+ "12345", "1234", "123", "12", "1", "c", "b", "a",
+ }, rdb.LRange(ctx, key, 0, -1).Val())
+ })
+
+ t.Run("Quick list encoding", func(t *testing.T) {
+ key := util.RandString(32, 64, util.Alpha)
+ value :=
"\x12\x01\x02\xc3%@z\az\x00\x00\x00\a\x00\xb5x\xe0+\x00\x026\xa2y\xe0\x18\x00\x02#\x8ez\xe0\x04\x00\t\x0f\x01\x01\x02\x01\x03\x01\x04\x01\xff\n\x00\x89\x14\xff>\xf8F\x0e="
+ require.NoError(t, rdb.Restore(ctx, key, 0, value).Err())
+ require.EqualValues(t, 7, rdb.LLen(ctx, key).Val())
+ require.EqualValues(t, []string{
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
+ "zzzzzzzzzzzzzz",
+ "1", "2", "3", "4",
+ }, rdb.LRange(ctx, key, 0, -1).Val())
+ require.EqualValues(t, -1, rdb.TTL(ctx, key).Val())
+ })
+}
+
+func TestRestore_Set(t *testing.T) {
+ srv := util.StartServer(t, map[string]string{})
+ defer srv.Close()
+
+ ctx := context.Background()
+ rdb := srv.NewClient()
+ defer func() { require.NoError(t, rdb.Close()) }()
+
+ t.Run("Set object encoding", func(t *testing.T) {
+ key := util.RandString(32, 64, util.Alpha)
+ value :=
"\x02\x05\x02ab\xc1\xd2\x04\x01a\x15012345678901234567890\x03abc\x06\x00s\xf8_\x01\xf3\xf56\xd8"
+ require.NoError(t, rdb.Restore(ctx, key, 0, value).Err())
+ require.EqualValues(t, []string{
+ "012345678901234567890", "1234", "a", "ab", "abc",
+ }, rdb.SMembers(ctx, key).Val())
+ })
+
+ t.Run("List pack encoding", func(t *testing.T) {
+ key := util.RandString(32, 64, util.Alpha)
+ value :=
"\x14\x15\x15\x00\x00\x00\x05\x00\x84abcd\x05\x01\x01\x02\x01\x03\x01\x04\x01\xff\x0b\x00\xc8h\xa3\xaf\x8b\x1f\xd4\xab"
+ require.NoError(t, rdb.Restore(ctx, key, 0, value).Err())
+ require.EqualValues(t, []string{"1", "2", "3", "4", "abcd"},
rdb.SMembers(ctx, key).Val())
+ })
+
+ t.Run("16bit INTSET encoding", func(t *testing.T) {
+ key := util.RandString(32, 64, util.Alpha)
+ value :=
"\x0b\x10\x02\x00\x00\x00\x04\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x0b\x00\xc4}\x10TeTI<"
+ require.NoError(t, rdb.Restore(ctx, key, 0, value).Err())
+ require.EqualValues(t, []string{"1", "2", "3", "4"},
rdb.SMembers(ctx, key).Val())
+ })
+
+ t.Run("32bit INTSET encoding", func(t *testing.T) {
+ key := util.RandString(32, 64, util.Alpha)
+ value :=
"\x0b\x14\x04\x00\x00\x00\x03\x00\x00\x00\xd2\x04\x00\x005\x82\x00\x00@\xe2\x01\x00\x0b\x00h\xc8u\x0b/\x95\\X"
+ require.NoError(t, rdb.Restore(ctx, key, 0, value).Err())
+ require.EqualValues(t, []string{"1234", "123456", "33333"},
rdb.SMembers(ctx, key).Val())
+ })
+
+ t.Run("64bit INTSET encoding", func(t *testing.T) {
+ key := util.RandString(32, 64, util.Alpha)
+ value := "\x0b\xc3\x1b \x04\b\x00\x00\x00\x03
\x03\x04\xe3\x80^\xef\x0c
\a\x00\xe4\xa0\a\x00\xe5`\a\x01\x00\x00\x0b\x00Sb\xaf\xbf\x1c\xb6J="
+ require.NoError(t, rdb.Restore(ctx, key, 0, value).Err())
+ require.EqualValues(t, []string{"55555555555", "55555555556",
"55555555557"}, rdb.SMembers(ctx, key).Val())
+ })
+}
+
+func TestRestoreWithTTL(t *testing.T) {
+ srv := util.StartServer(t, map[string]string{})
+ defer srv.Close()
+
+ ctx := context.Background()
+ rdb := srv.NewClient()
+ defer func() { require.NoError(t, rdb.Close()) }()
+
+ key := util.RandString(32, 64, util.Alpha)
+ value :=
"\x02\x05\x02ab\xc1\xd2\x04\x01a\x15012345678901234567890\x03abc\x06\x00s\xf8_\x01\xf3\xf56\xd8"
+ require.NoError(t, rdb.Restore(ctx, key, 0, value).Err())
+ require.EqualValues(t, -1, rdb.TTL(ctx, key).Val())
+ require.EqualValues(t, []string{
+ "012345678901234567890", "1234", "a", "ab", "abc",
+ }, rdb.SMembers(ctx, key).Val())
+
+ // Cannot restore to an existing key.
+ newValue :=
"\x0b\x10\x02\x00\x00\x00\x04\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x0b\x00\xc4}\x10TeTI<"
+ require.EqualError(t, rdb.Restore(ctx, key, 0, newValue).Err(),
+ "ERR target key name already exists.")
+
+ // Restore exists key with the replacement flag
+ require.NoError(t, rdb.RestoreReplace(ctx, key, 10*time.Second,
newValue).Err())
+ require.EqualValues(t, []string{"1", "2", "3", "4"}, rdb.SMembers(ctx,
key).Val())
+ require.Greater(t, rdb.TTL(ctx, key).Val(), 5*time.Second)
+ require.LessOrEqual(t, rdb.TTL(ctx, key).Val(), 10*time.Second)
+}
+
+func TestRestoreWithExpiredTTL(t *testing.T) {
+ srv := util.StartServer(t, map[string]string{})
+ defer srv.Close()
+
+ ctx := context.Background()
+ rdb := srv.NewClient()
+ defer func() { require.NoError(t, rdb.Close()) }()
+
+ key := util.RandString(32, 64, util.Alpha)
+ value := "\x00\x03bar\n\x00\xe6\xbeI`\xeef\xfd\x17"
+
+ // Wrong TTL value
+ require.EqualError(t, rdb.Do(ctx, "RESTORE", key, -1, value).Err(),
"ERR out of numeric range")
+ require.NoError(t, rdb.Do(ctx, "RESTORE", key, 0, value).Err())
+ require.Equal(t, "bar", rdb.Get(ctx, key).Val())
+ require.NoError(t, rdb.Do(ctx, "RESTORE", key, 1111, value, "REPLACE",
"ABSTTL").Err())
+ require.EqualError(t, rdb.Get(ctx, key).Err(), redis.Nil.Error())
+}
diff --git a/tests/gocase/util/random.go b/tests/gocase/util/random.go
index 0c290a68..4082a771 100644
--- a/tests/gocase/util/random.go
+++ b/tests/gocase/util/random.go
@@ -23,6 +23,7 @@ import (
"fmt"
"math/rand"
"strings"
+ "time"
)
func RandPath[T any](f ...func() T) T {
@@ -57,7 +58,8 @@ const (
)
func RandString(min, max int, typ RandStringType) string {
- length := min + rand.Intn(max-min+1)
+ r := rand.New(rand.NewSource(time.Now().UnixNano()))
+ length := min + r.Intn(max-min+1)
var minVal, maxVal int
switch typ {