Commit 4d5a9ac4 authored by root's avatar root

couchbase without handling rebalance case

parent ce7767a8
......@@ -10,29 +10,11 @@ config_setting(
visibility = ["//visibility:public"],
)
config_setting(
name = "with_thrift",
define_values = {"with_thrift": "true"},
visibility = ["//visibility:public"],
)
config_setting(
name = "unittest",
define_values = {"unittest": "true"},
)
config_setting(
name = "darwin",
values = {"cpu": "darwin"},
visibility = ["//visibility:public"],
)
config_setting(
name = "linux",
values = {"cpu": "linux"},
visibility = ["//visibility:public"],
)
COPTS = [
"-DBTHREAD_USE_FAST_PTHREAD_MUTEX",
"-D__const__=",
......@@ -46,40 +28,16 @@ COPTS = [
] + select({
":with_glog": ["-DBRPC_WITH_GLOG=1"],
"//conditions:default": ["-DBRPC_WITH_GLOG=0"],
}) + select({
":with_thrift": ["-DENABLE_THRIFT_FRAMED_PROTOCOL=1"],
"//conditions:default": [""],
})
LINKOPTS = [
"-lpthread",
"-ldl",
"-lz",
"-lrt",
"-lssl",
"-lcrypto",
] + select({
":darwin": [
"-framework CoreFoundation",
"-framework CoreGraphics",
"-framework CoreData",
"-framework CoreText",
"-framework Security",
"-framework Foundation",
"-Wl,-U,_MallocExtension_ReleaseFreeMemory",
"-Wl,-U,_ProfilerStart",
"-Wl,-U,_ProfilerStop",
"-Wl,-U,_RegisterThriftProtocol",
],
"//conditions:default": [
"-lrt",
],
}) + select({
":with_thrift": [
"-lthriftnb",
"-levent",
"-lthrift"],
"//conditions:default": [],
})
"-ldl",
"-lz",
]
genrule(
name = "config_h",
......@@ -118,6 +76,10 @@ BUTIL_SRCS = [
"src/butil/third_party/snappy/snappy-stubs-internal.cc",
"src/butil/third_party/snappy/snappy.cc",
"src/butil/third_party/murmurhash3/murmurhash3.cpp",
"src/butil/third_party/libvbucket/cJSON.c",
"src/butil/third_party/libvbucket/crc32.c",
"src/butil/third_party/libvbucket/ketama.c",
"src/butil/third_party/libvbucket/vbucket.c",
"src/butil/arena.cpp",
"src/butil/at_exit.cc",
"src/butil/atomicops_internals_x86_gcc.cc",
......@@ -145,6 +107,7 @@ BUTIL_SRCS = [
"src/butil/files/scoped_file.cc",
"src/butil/files/scoped_temp_dir.cc",
"src/butil/file_util.cc",
"src/butil/file_util_linux.cc",
"src/butil/file_util_posix.cc",
"src/butil/guid.cc",
"src/butil/guid_posix.cc",
......@@ -159,7 +122,6 @@ BUTIL_SRCS = [
"src/butil/memory/weak_ptr.cc",
"src/butil/posix/file_descriptor_shuffle.cc",
"src/butil/posix/global_descriptors.cc",
"src/butil/process_util.cc",
"src/butil/rand_util.cc",
"src/butil/rand_util_posix.cc",
"src/butil/fast_rand.cpp",
......@@ -175,6 +137,7 @@ BUTIL_SRCS = [
"src/butil/strings/string_util.cc",
"src/butil/strings/string_util_constants.cc",
"src/butil/strings/stringprintf.cc",
"src/butil/strings/sys_string_conversions_posix.cc",
"src/butil/strings/utf_offset_string_conversions.cc",
"src/butil/strings/utf_string_conversion_utils.cc",
"src/butil/strings/utf_string_conversions.cc",
......@@ -182,6 +145,7 @@ BUTIL_SRCS = [
"src/butil/synchronization/condition_variable_posix.cc",
"src/butil/synchronization/waitable_event_posix.cc",
"src/butil/threading/non_thread_safe_impl.cc",
"src/butil/threading/platform_thread_linux.cc",
"src/butil/threading/platform_thread_posix.cc",
"src/butil/threading/simple_thread.cc",
"src/butil/threading/thread_checker_impl.cc",
......@@ -217,82 +181,8 @@ BUTIL_SRCS = [
"src/butil/containers/case_ignored_flat_map.cpp",
"src/butil/iobuf.cpp",
"src/butil/popen.cpp",
] + select({
":darwin": [
"src/butil/time/time_mac.cc",
"src/butil/mac/scoped_mach_port.cc",
],
"//conditions:default": [
"src/butil/file_util_linux.cc",
"src/butil/threading/platform_thread_linux.cc",
"src/butil/strings/sys_string_conversions_posix.cc",
],
})
]
objc_library(
name = "macos_lib",
hdrs = [":config_h",
"src/butil/atomicops.h",
"src/butil/atomicops_internals_atomicword_compat.h",
"src/butil/atomicops_internals_mac.h",
"src/butil/base_export.h",
"src/butil/basictypes.h",
"src/butil/build_config.h",
"src/butil/compat.h",
"src/butil/compiler_specific.h",
"src/butil/containers/hash_tables.h",
"src/butil/debug/debugger.h",
"src/butil/debug/leak_annotations.h",
"src/butil/file_util.h",
"src/butil/file_descriptor_posix.h",
"src/butil/files/file_path.h",
"src/butil/files/file.h",
"src/butil/files/scoped_file.h",
"src/butil/lazy_instance.h",
"src/butil/logging.h",
"src/butil/mac/bundle_locations.h",
"src/butil/mac/foundation_util.h",
"src/butil/mac/scoped_cftyperef.h",
"src/butil/mac/scoped_typeref.h",
"src/butil/macros.h",
"src/butil/memory/aligned_memory.h",
"src/butil/memory/scoped_policy.h",
"src/butil/memory/scoped_ptr.h",
"src/butil/move.h",
"src/butil/port.h",
"src/butil/posix/eintr_wrapper.h",
"src/butil/scoped_generic.h",
"src/butil/strings/string16.h",
"src/butil/strings/string_piece.h",
"src/butil/strings/string_util.h",
"src/butil/strings/string_util_posix.h",
"src/butil/strings/sys_string_conversions.h",
"src/butil/synchronization/lock.h",
"src/butil/time/time.h",
"src/butil/time.h",
"src/butil/third_party/dynamic_annotations/dynamic_annotations.h",
"src/butil/threading/platform_thread.h",
"src/butil/threading/thread_restrictions.h",
"src/butil/threading/thread_id_name_manager.h",
"src/butil/type_traits.h",
],
non_arc_srcs = [
"src/butil/mac/bundle_locations.mm",
"src/butil/mac/foundation_util.mm",
"src/butil/file_util_mac.mm",
"src/butil/threading/platform_thread_mac.mm",
"src/butil/strings/sys_string_conversions_mac.mm",
],
deps = [
"@com_github_gflags_gflags//:gflags",
] + select({
":with_glog": ["@com_github_google_glog//:glog"],
"//conditions:default": [],
}),
includes = ["src/"],
enable_modules = True,
tags = ["manual"],
)
cc_library(
name = "butil",
......@@ -313,7 +203,6 @@ cc_library(
"@com_github_gflags_gflags//:gflags",
] + select({
":with_glog": ["@com_github_google_glog//:glog"],
":darwin": [":macos_lib"],
"//conditions:default": [],
}),
includes = [
......@@ -458,17 +347,7 @@ cc_library(
srcs = glob([
"src/brpc/*.cpp",
"src/brpc/**/*.cpp",
],
exclude = [
"src/brpc/thrift_service.cpp",
"src/brpc/thrift_message.cpp",
"src/brpc/policy/thrift_protocol.cpp",
]) + select({
":with_thrift" : glob([
"src/brpc/thrift*.cpp",
"src/brpc/**/thrift*.cpp"]),
"//conditions:default" : [],
}),
]),
hdrs = glob([
"src/brpc/*.h",
"src/brpc/**/*.h"
......
......@@ -2,9 +2,7 @@ cmake_minimum_required(VERSION 2.8.10)
project(brpc C CXX)
# Enable MACOSX_RPATH. Run "cmake --help-policy CMP0042" for policy details.
if(POLICY CMP0042)
cmake_policy(SET CMP0042 NEW)
endif()
cmake_policy(SET CMP0042 NEW)
set(BRPC_VERSION 0.9.0)
......@@ -22,14 +20,14 @@ else()
message(WARNING "You are using an unsupported compiler! Compilation has only been tested with Clang and GCC.")
endif()
option(WITH_GLOG "With glog" OFF)
option(BRPC_WITH_GLOG "With glog" OFF)
option(DEBUG "Print debug logs" OFF)
option(WITH_DEBUG_SYMBOLS "With debug symbols" ON)
option(WITH_THRIFT "With thrift framed protocol supported" OFF)
option(BRPC_WITH_THRIFT "With thrift framed protocol supported" OFF)
option(BUILD_UNIT_TESTS "Whether to build unit tests" OFF)
set(WITH_GLOG_VAL "0")
if(WITH_GLOG)
if(BRPC_WITH_GLOG)
set(WITH_GLOG_VAL "1")
endif()
......@@ -37,10 +35,10 @@ if(WITH_DEBUG_SYMBOLS)
set(DEBUG_SYMBOL "-g")
endif()
if(WITH_THRIFT)
if(BRPC_WITH_THRIFT)
set(THRIFT_CPP_FLAG "-DENABLE_THRIFT_FRAMED_PROTOCOL")
set(THRIFTNB_LIB "thriftnb")
set(THRIFT_LIB "thrift")
set(THRIFT_LIB "thriftnb")
message("Enable thrift framed procotol")
endif()
include(GNUInstallDirs)
......@@ -121,7 +119,7 @@ if ((NOT LEVELDB_INCLUDE_PATH) OR (NOT LEVELDB_LIB))
message(FATAL_ERROR "Fail to find leveldb")
endif()
if(WITH_GLOG)
if(BRPC_WITH_GLOG)
find_path(GLOG_INCLUDE_PATH NAMES glog/logging.h)
find_library(GLOG_LIB NAMES glog)
if((NOT GLOG_INCLUDE_PATH) OR (NOT GLOG_LIB))
......@@ -157,7 +155,6 @@ set(DYNAMIC_LIB
${PROTOC_LIB}
${CMAKE_THREAD_LIBS_INIT}
${THRIFT_LIB}
${THRIFTNB_LIB}
${OPENSSL_LIBRARIES}
${OPENSSL_CRYPTO_LIBRARY}
dl
......@@ -165,7 +162,7 @@ set(DYNAMIC_LIB
)
set(BRPC_PRIVATE_LIBS "-lgflags -lprotobuf -lleveldb -lprotoc -lssl -lcrypto -ldl -lz")
if(WITH_GLOG)
if(BRPC_WITH_GLOG)
set(DYNAMIC_LIB ${DYNAMIC_LIB} ${GLOG_LIB})
set(BRPC_PRIVATE_LIBS "${BRPC_PRIVATE_LIBS} -lglog")
endif()
......@@ -184,7 +181,8 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
"-framework Foundation"
"-Wl,-U,_MallocExtension_ReleaseFreeMemory"
"-Wl,-U,_ProfilerStart"
"-Wl,-U,_ProfilerStop")
"-Wl,-U,_ProfilerStop"
"-Wl,-U,_RegisterThriftProtocol")
endif()
# for *.so
......@@ -207,6 +205,10 @@ set(BUTIL_SOURCES
${CMAKE_SOURCE_DIR}/src/butil/third_party/snappy/snappy-stubs-internal.cc
${CMAKE_SOURCE_DIR}/src/butil/third_party/snappy/snappy.cc
${CMAKE_SOURCE_DIR}/src/butil/third_party/murmurhash3/murmurhash3.cpp
${CMAKE_SOURCE_DIR}/src/butil/third_party/libvbucket/cJSON.c
${CMAKE_SOURCE_DIR}/src/butil/third_party/libvbucket/crc32.c
${CMAKE_SOURCE_DIR}/src/butil/third_party/libvbucket/ketama.c
${CMAKE_SOURCE_DIR}/src/butil/third_party/libvbucket/vbucket.c
${CMAKE_SOURCE_DIR}/src/butil/arena.cpp
${CMAKE_SOURCE_DIR}/src/butil/at_exit.cc
${CMAKE_SOURCE_DIR}/src/butil/atomicops_internals_x86_gcc.cc
......@@ -328,17 +330,6 @@ file(GLOB_RECURSE BVAR_SOURCES "${CMAKE_SOURCE_DIR}/src/bvar/*.cpp")
file(GLOB_RECURSE BTHREAD_SOURCES "${CMAKE_SOURCE_DIR}/src/bthread/*.cpp")
file(GLOB_RECURSE JSON2PB_SOURCES "${CMAKE_SOURCE_DIR}/src/json2pb/*.cpp")
file(GLOB_RECURSE BRPC_SOURCES "${CMAKE_SOURCE_DIR}/src/brpc/*.cpp")
file(GLOB_RECURSE THRIFT_SOURCES "thrift*.cpp")
if(WITH_THRIFT)
message("brpc compile with thrift proctol")
else()
# Remove thrift sources
foreach(v ${THRIFT_SOURCES})
list(REMOVE_ITEM BRPC_SOURCES ${v})
endforeach()
set(THRIFT_SOURCES "")
endif()
set(MCPACK2PB_SOURCES
${CMAKE_SOURCE_DIR}/src/mcpack2pb/field_type.cpp
......@@ -379,7 +370,6 @@ set(SOURCES
${JSON2PB_SOURCES}
${MCPACK2PB_SOURCES}
${BRPC_SOURCES}
${THRIFT_SOURCES}
)
add_subdirectory(src)
......
......@@ -47,6 +47,10 @@ BUTIL_SOURCES = \
src/butil/third_party/snappy/snappy-stubs-internal.cc \
src/butil/third_party/snappy/snappy.cc \
src/butil/third_party/murmurhash3/murmurhash3.cpp \
src/butil/third_party/libvbucket/cJSON.c \
src/butil/third_party/libvbucket/crc32.c \
src/butil/third_party/libvbucket/ketama.c \
src/butil/third_party/libvbucket/vbucket.c \
src/butil/arena.cpp \
src/butil/at_exit.cc \
src/butil/atomicops_internals_x86_gcc.cc \
......
......@@ -124,6 +124,7 @@ struct ChannelOptions {
class Channel : public ChannelBase {
friend class Controller;
friend class SelectiveChannel;
friend class CouchbaseChannel;
public:
Channel(ProfilerLinker = ProfilerLinker());
~Channel();
......
......@@ -102,6 +102,8 @@ friend class ParallelChannelDone;
friend class ControllerPrivateAccessor;
friend class ServerPrivateAccessor;
friend class SelectiveChannel;
friend class CouchbaseChannel;
friend class CouchbaseDone;
friend class schan::Sender;
friend class schan::SubDone;
friend class policy::OnServerStreamCreated;
......@@ -142,6 +144,15 @@ public:
void set_timeout_ms(int64_t timeout_ms);
int64_t timeout_ms() const { return _timeout_ms; }
// Set timeout of the request trace deadline (in milliseconds)
void set_request_trace_timeout_ms(int64_t timeout_ms);
// Set the request trace deadline. We suggest you to use
// set_request_trace_timeout_ms for root request.
void set_request_trace_deadline(int64_t request_trace_deadline) {
_request_trace_deadline = request_trace_deadline;
}
// Set/get the delay to send backup request in milliseconds. Use
// ChannelOptions.backup_request_ms on unset.
void set_backup_request_ms(int64_t timeout_ms);
......@@ -370,6 +381,11 @@ public:
// Get the data attached to a mongo session(practically a socket).
MongoContext* mongo_session_data() { return _mongo_session_data.get(); }
// Get a request trace deadline timestamp.
int64_t request_trace_deadline() const;
// Get remain milliseconds to the request trace deadline.
int64_t get_request_trace_remain_ms() const;
// -------------------------------------------------------------------
// Both-side methods.
// Following methods can be called from both client and server. But they
......@@ -451,7 +467,14 @@ public:
void set_idl_result(int64_t result) { _idl_result = result; }
int64_t idl_result() const { return _idl_result; }
const std::string& thrift_method_name() { return _thrift_method_name; }
bool has_request_trace_deadline() const {
return _request_trace_deadline != UNSET_MAGIC_NUM;
}
void set_thrift_method_name(const std::string& method_name) {
_thrift_method_name = method_name;
}
std::string thrift_method_name() { return _thrift_method_name; }
private:
struct CompletionInfo {
......@@ -625,6 +648,10 @@ private:
int32_t _timeout_ms;
int32_t _connect_timeout_ms;
int32_t _backup_request_ms;
// Deadline of this rpc trace(since the Epoch in microseconds),
// set by root request of the rpc trace, and each child node of trace
// can judge root rpc request timed out or not according to the value.
int64_t _request_trace_deadline;
// Deadline of this RPC (since the Epoch in microseconds).
int64_t _abstime_us;
// Timer registered to trigger RPC timeout event
......@@ -686,6 +713,7 @@ private:
// Thrift method name, only used when thrift protocol enabled
std::string _thrift_method_name;
uint32_t _thrift_seq_id;
};
// Advises the RPC system that the caller desires that the RPC call be
......
// Copyright (c) 2018 Qiyi, Inc.
//
// Licensed 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.
// Authors: Daojin Cai (caidaojin@qiyi.com)
#include "brpc/couchbase.h"
#include "brpc/policy/memcache_binary_header.h"
#include "butil/string_printf.h"
#include "butil/sys_byteorder.h"
namespace brpc {
int CouchbaseRequest::ParseRequest(
std::string* key, policy::MemcacheBinaryCommand* command) const {
const size_t n = _buf.size();
policy::MemcacheRequestHeader header;
if (n < sizeof(header)) {
return -1;
}
_buf.copy_to(&header, sizeof(header));
// TODO: need check header.total_body_length
if (header.key_length == 0) {
return 1;
}
*command = static_cast<policy::MemcacheBinaryCommand>(header.command);
_buf.copy_to(key, header.key_length, sizeof(header) + header.extras_length);
return 0;
}
bool CouchbaseRequest::ReplicasGet(const butil::StringPiece& key) {
const policy::MemcacheRequestHeader header = {
policy::MC_MAGIC_REQUEST,
0x83,
butil::HostToNet16(key.size()),
0,
policy::MC_BINARY_RAW_BYTES,
0,
butil::HostToNet32(key.size()),
0,
0
};
if (_buf.append(&header, sizeof(header))) {
return false;
}
if (_buf.append(key.data(), key.size())) {
return false;
}
++_pipelined_count;
return true;
}
bool CouchbaseResponse::GetStatus(Status* st) {
const size_t n = _buf.size();
policy::MemcacheResponseHeader header;
if (n < sizeof(header)) {
butil::string_printf(&_err, "buffer is too small to contain a header");
return false;
}
_buf.copy_to(&header, sizeof(header));
if (n < sizeof(header) + header.total_body_length) {
butil::string_printf(&_err, "response=%u < header=%u + body=%u",
(unsigned)n, (unsigned)sizeof(header), header.total_body_length);
return false;
}
*st = static_cast<Status>(header.status);
return true;
}
} // namespace brpc
......@@ -18,6 +18,7 @@
#define BRPC_COUCHBASE_H
#include "brpc/memcache.h"
#include "brpc/policy/memcache_binary_header.h"
namespace brpc {
......@@ -102,6 +103,11 @@ public:
MemcacheRequest::CopyFrom(from);
}
int ParseRequest(std::string* key,
policy::MemcacheBinaryCommand* command) const;
bool ReplicasGet(const butil::StringPiece& key);
private:
void MergeFrom(const CouchbaseRequest& from);
......@@ -122,6 +128,8 @@ public:
MemcacheResponse::CopyFrom(from);
}
bool GetStatus(Status* status);
private:
void MergeFrom(const CouchbaseResponse& from);
......
......@@ -15,43 +15,333 @@
// Authors: Daojin Cai (caidaojin@qiyi.com)
#include "brpc/couchbase_channel.h"
#include "brpc/policy/couchbase_authenticator.h"
#include "brpc/progressive_reader.h"
#include "bthread/bthread.h"
#include "butil/base64.h"
#include "butil/third_party/libvbucket/hash.h"
#include "butil/third_party/libvbucket/vbucket.h"
namespace brpc {
DEFINE_string(couchbase_authorization_http_basic, "",
"Http basic authorization of couchbase");
DEFINE_string(couchbase_bucket_init_string, "",
"If the string is set, 'CouchbaseServerListener' will build vbucket map"
"directly by parsing from this string in initialization");
DEFINE_string(couchbase_bucket_streaming_url,
"/pools/default/bucketsStreaming/",
"Monitor couchbase vbuckets map through this url");
DEFINE_int32(listener_retry_times, 5,
"Retry times to create couchbase vbucket map monitoring connection."
"Listen thread will sleep a while when reach this times.");
DEFINE_int32(listener_sleep_interval_ms, 100,
"Listen thread sleep for the number of milliseconds after creating"
"vbucket map monitoring connection failure.");
DEFINE_bool(retry_during_rebalance, true,
"A swith indicating whether to open retry during rebalance");
DEFINE_bool(replicas_read_flag, false,
"Read replicas for get request in case of master node failure."
"This does not ensure that the data is the most current.");
namespace {
const butil::StringPiece kSeparator("\n\n\n\n", 4);
}
class CouchbaseServerListener;
enum RetryReason {
RPC_FAILED = 0,
WRONG_SERVER = 1,
RESPONSE_FAULT = 2,
};
class VBucketMapReader : public ProgressiveReader {
public:
VBucketMapReader(CouchbaseServerListener* listener) : _listener(listener) {}
~VBucketMapReader() = default;
virtual butil::Status OnReadOnePart(const void* data, size_t length);
virtual void OnEndOfMessage(const butil::Status& status);
void Attach() { _attach = true; }
void Detach() { _attach = false; }
bool IsAttached() { return _attach; }
// The couchbase channel has been distructed.
void Destroy() { _listener = nullptr; }
public:
VBucketMapReader(const VBucketMapReader&) = delete;
VBucketMapReader& operator=(const VBucketMapReader&) = delete;
// It is monitoring vbucket map if it is true.
bool _attach = false;
CouchbaseServerListener* _listener;
std::string _buf;
butil::Mutex _mutex;
};
class CouchbaseServerListener {
public:
CouchbaseServerListener(const char* server_addr,
const CouchbaseChannel* channel)
: _server_addr(server_addr), _channel(channel) {
//TODO: Init vbucket map for first time.
CHECK(bthread_start_background(
&_bthread_id, nullptr, ListenThread, this) == 0)
<< "Failed to start ListenThread.";
CouchbaseServerListener(const char* server_addr, CouchbaseChannel* channel)
: _server_addr(server_addr),
_url(FLAGS_couchbase_bucket_streaming_url),
_cb_channel(channel),
_reader(new VBucketMapReader(this)) {
Init();
}
~CouchbaseServerListener() {
bthread_stop(_bthread_id);
bthread_join(_bthread_id, nullptr);
};
~CouchbaseServerListener();
void UpdateVBucketMap(butil::VBUCKET_CONFIG_HANDLE vbucket);
void CreateListener();
private:
CouchbaseServerListener(const CouchbaseServerListener&) = delete;
CouchbaseServerListener& operator=(const CouchbaseServerListener&) = delete;
void Init();
void InitVBucketMap(const std::string& str);
static void* ListenThread(void* arg);
bthread_t _bthread_id;
bthread_t _listen_bth;
// Server address list of couchbase servers. From these servers(host:port),
// we can monitor vbucket map.
const std::string _server_addr;
const CouchbaseChannel* _channel;
std::vector<std::string> _vbucket_servers;
// REST/JSON url to monitor vbucket map.
const std::string _url;
std::string _auth;
CouchbaseChannel* _cb_channel;
// Monitor couchbase vbuckets map on this channel.
Channel _listen_channel;
// If _reader is not attached to listen socket, it will be released in
// CouchbaseServerListener desconstruction. Otherwise, it will be released
// by itself.
VBucketMapReader* _reader;
};
//TODO: Get current vbucket map of couchbase server
butil::Status VBucketMapReader::OnReadOnePart(const void* data, size_t length) {
BAIDU_SCOPED_LOCK(_mutex);
// If '_listener' is desconstructed, return error status directly.
if (_listener == nullptr) {
return butil::Status(-1, "Couchbase channel is destroyed");
}
_buf.append(static_cast<const char*>(data), length);
size_t pos = 0;
size_t new_pos = _buf.find(kSeparator.data(), pos, kSeparator.size());
while (new_pos != std::string::npos) {
std::string complete = _buf.substr(pos, new_pos);
butil::VBUCKET_CONFIG_HANDLE vb =
butil::vbucket_config_parse_string(complete.c_str());
_listener->UpdateVBucketMap(vb);
if (vb != nullptr) {
butil::vbucket_config_destroy(vb);
}
pos = new_pos + kSeparator.size();
new_pos = _buf.find(kSeparator.data(), pos, kSeparator.size());
}
if (pos < _buf.size()) {
_buf = _buf.substr(pos);
} else {
_buf.clear();
}
return butil::Status::OK();
}
void VBucketMapReader::OnEndOfMessage(const butil::Status& status) {
{
BAIDU_SCOPED_LOCK(_mutex);
if (_listener != nullptr) {
_buf.clear();
Detach();
_listener->CreateListener();
return;
}
}
// If '_listener' is desconstructed, release this object.
std::unique_ptr<VBucketMapReader> release(this);
}
void CouchbaseServerListener::Init() {
if (!FLAGS_couchbase_authorization_http_basic.empty()) {
butil::Base64Encode(FLAGS_couchbase_authorization_http_basic, &_auth);
_auth = "Basic " + _auth;
} else {
std::string auth_str = _cb_channel->GetAuthentication();
if (!auth_str.empty()) {
butil::Base64Encode(auth_str, &_auth);
_auth = "Basic " + _auth;
}
}
ChannelOptions options;
options.protocol = PROTOCOL_HTTP;
CHECK(_listen_channel.Init(_server_addr.c_str(), "rr", &options) == 0)
<< "Failed to init listen channel.";
if (!FLAGS_couchbase_bucket_init_string.empty()) {
InitVBucketMap(FLAGS_couchbase_bucket_init_string);
}
CreateListener();
}
CouchbaseServerListener::~CouchbaseServerListener() {
std::unique_lock<butil::Mutex> mu(_reader->_mutex);
bthread_stop(_listen_bth);
bthread_join(_listen_bth, nullptr);
if (!_reader->IsAttached()) {
mu.unlock();
std::unique_ptr<VBucketMapReader> p(_reader);
} else {
_reader->Destroy();
}
}
void CouchbaseServerListener::InitVBucketMap(const std::string& str) {
butil::VBUCKET_CONFIG_HANDLE vb =
butil::vbucket_config_parse_string(str.c_str());
UpdateVBucketMap(vb);
if (vb != nullptr) {
butil::vbucket_config_destroy(vb);
}
}
void* CouchbaseServerListener::ListenThread(void* arg) {
CouchbaseServerListener* listener =
static_cast<CouchbaseServerListener*>(arg);
while (true) {
listener->_reader->Detach();
Controller cntl;
int i = 0;
for (; i != FLAGS_listener_retry_times; ++i) {
if (!listener->_auth.empty()) {
cntl.http_request().SetHeader("Authorization", listener->_auth);
}
cntl.http_request().uri() = listener->_url;
cntl.response_will_be_read_progressively();
listener->_listen_channel.CallMethod(nullptr, &cntl,
nullptr, nullptr, nullptr);
if (cntl.Failed()) {
LOG(ERROR) << "Failed to create vbucket map reader: "
<< cntl.ErrorText();
cntl.Reset();
continue;
}
break;
}
if (i == FLAGS_listener_retry_times) {
if (bthread_usleep(FLAGS_listener_sleep_interval_ms * 1000) < 0) {
if (errno == ESTOP) {
LOG(INFO) << "ListenThread is stopped.";
break;
}
LOG(ERROR) << "Failed to sleep.";
}
continue;
}
listener->_reader->Attach();
cntl.ReadProgressiveAttachmentBy(listener->_reader);
break;
}
return nullptr;
}
void CouchbaseServerListener::CreateListener() {
// TODO: keep one listen thread waiting on futex.
CHECK(bthread_start_urgent(
&_listen_bth, nullptr, ListenThread, this) == 0)
<< "Failed to start listen thread.";
}
void CouchbaseServerListener::UpdateVBucketMap(
butil::VBUCKET_CONFIG_HANDLE vb_conf) {
if (vb_conf == nullptr) {
LOG(ERROR) << "Null VBUCKET_CONFIG_HANDLE.";
return;
}
// TODO: ketama distribution
if (butil::vbucket_config_get_distribution_type(vb_conf)
== butil::VBUCKET_DISTRIBUTION_KETAMA) {
LOG(FATAL) << "Not support ketama distribution.";
return;
}
const CouchbaseChannelMap& channel_map = _cb_channel->GetChannelMap();
int vb_num = butil::vbucket_config_get_num_vbuckets(vb_conf);
int replicas_num = butil::vbucket_config_get_num_replicas(vb_conf);
int server_num = butil::vbucket_config_get_num_servers(vb_conf);
std::vector<std::vector<int>> vbuckets(vb_num);
std::vector<std::vector<int>> fvbuckets;
std::vector<std::string> servers(server_num);
std::vector<std::string> added_servers;
std::vector<std::string> removed_servers;
for (int i = 0; i != vb_num; ++i) {
vbuckets[i].resize(replicas_num + 1, -1);
vbuckets[i][0] = butil::vbucket_get_master(vb_conf, i);
for (int j = 1; j <= replicas_num; ++j) {
vbuckets[i][j] = butil::vbucket_get_replica(vb_conf, i, j);
}
}
for (int i = 0; i != server_num; ++i) {
servers[i] = butil::vbucket_config_get_server(vb_conf, i);
const auto iter = channel_map.find(servers[i]);
if (iter == channel_map.end()) {
added_servers.emplace_back(servers[i]);
}
}
_cb_channel->UpdateVBucketServerMap(replicas_num, vbuckets, fvbuckets,
servers, added_servers, removed_servers);
}
class VBucketContext {
uint64_t _version = 0;
int _server_type = 0;
std::string _forward_master;
std::string _master;
std::string _key;
policy::MemcacheBinaryCommand _command;
CouchbaseRequest _request;
CouchbaseRequest _replicas_req;
};
class CouchbaseDone : public google::protobuf::Closure {
public:
CouchbaseDone(CouchbaseChannel* cb_channel, brpc::Controller* cntl,
CouchbaseResponse* response, google::protobuf::Closure* done)
: _cb_channel(cb_channel), _done(done),
_cntl(cntl), _response(response) { }
void Run();
private:
CouchbaseChannel* _cb_channel;
google::protobuf::Closure* _done;
brpc::Controller* _cntl;
CouchbaseResponse* _response;
VBucketContext _vb_context;
};
void CouchbaseDone::Run() {
std::unique_ptr<CouchbaseDone> self_guard(this);
while(FLAGS_retry_during_rebalance) {
//TODO: retry in case of rebalance/failover.
break;
}
_done->Run();
}
CouchbaseChannel::CouchbaseChannel() {}
CouchbaseChannel::~CouchbaseChannel() {
_listener.reset(nullptr);
}
......@@ -61,15 +351,13 @@ int CouchbaseChannel::Init(const char* server_addr,
if (options != nullptr) {
if (options->protocol != PROTOCOL_UNKNOWN &&
options->protocol != PROTOCOL_MEMCACHE) {
LOG(FATAL) << "Failed to init channel due to invalid protoc "
LOG(FATAL) << "Failed to init channel due to invalid protocol "
<< options->protocol.name() << '.';
return -1;
}
_common_options = *options;
_common_options.protocol = PROTOCOL_MEMCACHE;
} else {
// TODO: use a default options.
}
_common_options.protocol = PROTOCOL_MEMCACHE;
auto ptr = new CouchbaseServerListener(server_addr, this);
if (ptr == nullptr) {
LOG(FATAL) << "Failed to init CouchbaseChannel to " << server_addr << '.';
......@@ -84,109 +372,150 @@ void CouchbaseChannel::CallMethod(const google::protobuf::MethodDescriptor* meth
const google::protobuf::Message* request,
google::protobuf::Message* response,
google::protobuf::Closure* done) {
bool success = false;
butil::StringPiece key;
if (GetKeyFromRequest(request, &key)) {
butil::DoublyBufferedData<VBucketServerMap>::ScopedPtr vbucket_map;
if(_vbucket_map.Read(&vbucket_map) == 0) {
Channel* mapped_channel = SelectChannel(key, vbucket_map.get());
if (mapped_channel != nullptr) {
mapped_channel->CallMethod(nullptr,
controller,
request,
response,
done);
success = true;
const CouchbaseRequest* req = reinterpret_cast<const CouchbaseRequest*>(request);
Controller* cntl = static_cast<Controller*>(controller);
Channel* channel = nullptr;
do {
std::string key;
policy::MemcacheBinaryCommand command;
// Do not support Flush/Version
if (req->ParseRequest(&key, &command) != 0) {
cntl->SetFailed("failed to parse key and command from request");
break;
}
} else {
LOG(ERROR) << "Failed to read vbucket map.";
{
butil::DoublyBufferedData<VBucketServerMap>::ScopedPtr vb_map;
if(_vbucket_map.Read(&vb_map) != 0) {
cntl->SetFailed(ENOMEM, "failed to read vbucket map");
break;
}
} else {
LOG(ERROR) << "Failed to get key from request.";
if (vb_map->_version == 0) {
cntl->SetFailed(ENODATA, "vbucket map is not initialize");
break;
}
const size_t vb_index = Hash(key, vb_map->_vbucket.size());
channel = SelectMasterChannel(vb_map.get(), vb_index);
if (channel == nullptr) {
cntl->SetFailed(ENODATA,"failed to get mapped channel");
break;
}
channel->CallMethod(nullptr, cntl, request, response, done);
}
if (!success) {
controller->SetFailed("Failed to send request");
while(FLAGS_retry_during_rebalance) {
// TODO: retry in case of rebalance/failover
break;
}
return;
} while (false);
if (cntl->FailedInline()) {
if (done) {
done->Run();
}
}
}
bool CouchbaseChannel::GetKeyFromRequest(const google::protobuf::Message* request,
butil::StringPiece* key) {
return true;
Channel* CouchbaseChannel::SelectMasterChannel(
const VBucketServerMap* vb_map, const size_t vb_index) {
return GetMappedChannel(GetMaster(vb_map, vb_index), vb_map);
}
const CouchbaseChannelMap& CouchbaseChannel::GetChannelMap() {
butil::DoublyBufferedData<VBucketServerMap>::ScopedPtr vbucket_map;
if(_vbucket_map.Read(&vbucket_map) != 0) {
LOG(FATAL) << "Failed to read vbucket map.";
}
return vbucket_map->_channel_map;
}
Channel* CouchbaseChannel::SelectChannel(
const butil::StringPiece& key, const VBucketServerMap* vbucket_map) {
size_t index = Hash(vbucket_map->_hash_algorithm,
key, vbucket_map->_vbucket_servers.size());
auto iter = vbucket_map->_channel_map.find(
vbucket_map->_vbucket_servers[index]);
if (iter != vbucket_map->_channel_map.end()) {
Channel* CouchbaseChannel::GetMappedChannel(const std::string* server,
const VBucketServerMap* vb_map) {
if (server == nullptr || server->empty()) {
return nullptr;
}
auto iter = vb_map->_channel_map.find(*server);
if (iter != vb_map->_channel_map.end()) {
return iter->second.get();
} else {
LOG(ERROR) << "Failed to find mapped channel.";
}
return nullptr;
}
//TODO: Get different hash algorithm if needed.
size_t CouchbaseChannel::Hash(const std::string& type,
const butil::StringPiece& key,
const size_t size) {
return 0;
const std::string* CouchbaseChannel::GetMaster(
const VBucketServerMap* vb_map, const size_t vb_index, int* index) {
if (vb_index < vb_map->_vbucket.size()) {
const int i = vb_map->_vbucket[vb_index][0];
if (i >= 0 && i < static_cast<int>(vb_map->_servers.size())) {
if (index != nullptr) {
*index = i;
}
return &vb_map->_servers[i];
}
}
return nullptr;
}
size_t CouchbaseChannel::Hash(const butil::StringPiece& key,
const size_t vbuckets_num) {
size_t digest = butil::hash_crc32(key.data(), key.size());
return digest & (vbuckets_num - 1);
}
bool CouchbaseChannel::UpdateVBucketServerMap(
const std::string* hash_algo,
std::vector<std::string>* vbucket_servers,
const std::vector<std::string>* added_vbuckets,
const std::vector<std::string>* removed_vbuckets) {
const int num_replicas,
std::vector<std::vector<int>>& vbucket,
std::vector<std::vector<int>>& fvbucket,
std::vector<std::string>& servers,
const std::vector<std::string>& added_servers,
const std::vector<std::string>& removed_servers) {
auto fn = std::bind(Update,
std::placeholders::_1,
&_common_options,
hash_algo,
vbucket_servers,
added_vbuckets,
removed_vbuckets);
num_replicas,
vbucket,
fvbucket,
servers,
added_servers,
removed_servers);
return _vbucket_map.Modify(fn);
}
bool CouchbaseChannel::Update(VBucketServerMap& vbucket_map,
const ChannelOptions* options,
const std::string* hash_algo,
std::vector<std::string>* vbucket_servers,
const std::vector<std::string>* added_vbuckets,
const std::vector<std::string>* removed_vbuckets) {
const int num_replicas,
std::vector<std::vector<int>>& vbucket,
std::vector<std::vector<int>>& fvbucket,
std::vector<std::string>& servers,
const std::vector<std::string>& added_servers,
const std::vector<std::string>& removed_servers) {
bool ret = true;
if (hash_algo != nullptr) {
vbucket_map._hash_algorithm = *hash_algo;
}
if (vbucket_servers != nullptr) {
vbucket_map._vbucket_servers.swap(*vbucket_servers);
}
if (added_vbuckets != nullptr) {
for (const auto& servers: *added_vbuckets) {
++(vbucket_map._version);
vbucket_map._num_replicas = num_replicas;
vbucket_map._vbucket.swap(vbucket);
vbucket_map._fvbucket.swap(fvbucket);
vbucket_map._servers.swap(servers);
if (!added_servers.empty()) {
for (const auto& server : added_servers) {
std::unique_ptr<Channel> p(new Channel());
if (p == nullptr) {
LOG(FATAL) << "Failed to init channel.";
return false;
}
if (p->Init(servers.c_str(), "rr", options) != 0) {
if (p->Init(server.c_str(), options) != 0) {
LOG(FATAL) << "Failed to init channel.";
return false;
}
auto pair = vbucket_map._channel_map.emplace(servers, std::move(p));
auto pair = vbucket_map._channel_map.emplace(server, std::move(p));
if (!pair.second) {
LOG(ERROR) << "Failed to add new channel to server: " << servers;
LOG(ERROR) << "Failed to add vbucket channel: " << server;
ret = false;
}
}
}
if (removed_vbuckets != nullptr) {
for (const auto& servers: *removed_vbuckets) {
auto n = vbucket_map._channel_map.erase(servers);
if (!removed_servers.empty()) {
for (const auto& server: removed_servers) {
auto n = vbucket_map._channel_map.erase(server);
if (n == 0) {
LOG(ERROR) << "Failed to remove channel to server: " << servers;
LOG(ERROR) << "Failed to remove vbucket channel: " << server;
ret = false;
}
}
......@@ -195,4 +524,44 @@ bool CouchbaseChannel::Update(VBucketServerMap& vbucket_map,
return ret;
}
std::string CouchbaseChannel::GetAuthentication() const {
const policy::CouchbaseAuthenticator* auth = reinterpret_cast<
const policy::CouchbaseAuthenticator*>(_common_options.auth);
std::string auth_str;
if (auth != nullptr && !auth->bucket_name().empty()
&& !auth->bucket_password().empty()) {
auth_str = auth->bucket_name() + ':' + auth->bucket_password();
}
return std::move(auth_str);
}
void CouchbaseChannel::Describe(std::ostream& os,
const DescribeOptions& options) {
os << "Couchbase channel[";
butil::DoublyBufferedData<VBucketServerMap>::ScopedPtr vbucket_map;
if(_vbucket_map.Read(&vbucket_map) != 0) {
os << "Failed to read _vbucket_map";
} else {
os << "n=" << vbucket_map->_servers.size() << ':';
for (const auto& servers : vbucket_map->_servers) {
os << ' ' << servers << ';';
}
}
os << "]";
}
int CouchbaseChannel::CheckHealth() {
butil::DoublyBufferedData<VBucketServerMap>::ScopedPtr vbucket_map;
if(_vbucket_map.Read(&vbucket_map) != 0) {
return -1;
}
for (const auto& channel_pair : vbucket_map->_channel_map) {
if (channel_pair.second->CheckHealth() != 0) {
return -1;
}
}
return 0;
}
} // namespace brpc
......@@ -21,21 +21,35 @@
#include <unordered_map>
#include "brpc/channel.h"
#include "brpc/couchbase.h"
#include "butil/containers/doubly_buffered_data.h"
namespace brpc {
using CouchbaseChannelMap =
std::unordered_map<std::string, std::unique_ptr<Channel>>;
class CouchbaseServerListener;
// A couchbase channel maps different key to sub memcache channel according to
// current vbuckets mapping. It retrieves current vbuckets mapping by maintain
// an connection for streaming updates from ther couchbase server.
//
// CAUTION:
// ========
// For async rpc, Should not delete this channel until rpc done.
class CouchbaseChannel : public ChannelBase/*non-copyable*/ {
friend class CouchbaseServerListener;
public:
CouchbaseChannel() = default;
CouchbaseChannel();
~CouchbaseChannel();
// You MUST initialize a couchbasechannel before using it. 'Server_addr'
// is address of couchbase server. 'options' is used for each channel to
// real servers of bucket. The protocol should be PROTOCOL_MEMCACHE.
// If 'options' is null, use default options.
// You MUST initialize a couchbasechannel before using it.
// 'Server_addr': address list of couchbase servers. On these addresses, we
// can get vbucket map.
// 'options': is used for each memcache channel of vbucket. The protocol
// should be PROTOCOL_MEMCACHE. If 'options' is null,
// use default options.
int Init(const char* server_addr, const ChannelOptions* options);
// TODO: Do not support pipeline mode now.
......@@ -46,51 +60,71 @@ public:
google::protobuf::Message* response,
google::protobuf::Closure* done);
void Describe(std::ostream& os, const DescribeOptions& options) const;
private:
// TODO: This struct describes map between vbucket and real memcache server.
// '_hash_algorithm': The hash algorithm couchbase used.
// '_vbucket_servers': server list of vbuckets, like "list://addr1:port1,
// addr2:port2...".
// '_channel_map': the channel for each vbucket.
void Describe(std::ostream& os, const DescribeOptions& options);
// Couchbase has two type of distribution used to map keys to servers.
// One is vbucket distribution and other is ketama distribution.
// This struct describes vbucket distribution of couchbase.
// 'num_replicas': the number of copies that will be stored on servers of one
// vbucket. Each vbucket must have this number of servers
// indexes plus one.
// '_vbucket': A zero-based indexed by vBucketId. The entries in the _vbucket
// are arrays of integers, where each integer is a zero-based
// index into the '_servers'.
// '_fvbucket': It is fast forward map with same struct as _vbucket. It is
// used to provide the final vBubcket-to-server map during the
// statrt of the rebalance.
// '_servers': all servers of a bucket.
// '_channel_map': the memcache channel for each server.
// TODO: support ketama vbucket distribution
struct VBucketServerMap {
std::string _hash_algorithm;
std::vector<std::string> _vbucket_servers;
std::unordered_map<std::string, std::unique_ptr<Channel>> _channel_map;
uint64_t _version = 0;
int _num_replicas = 0;
std::vector<std::vector<int>> _vbucket;
std::vector<std::vector<int>> _fvbucket;
std::vector<std::string> _servers;
CouchbaseChannelMap _channel_map;
};
private:
int CheckHealth();
bool GetKeyFromRequest(const google::protobuf::Message* request,
butil::StringPiece* key);
Channel* SelectMasterChannel(const VBucketServerMap* vb_map,
const size_t vb_index);
Channel* GetMappedChannel(const std::string* server,
const VBucketServerMap* vb_map);
Channel* SelectChannel(const butil::StringPiece& key,
const VBucketServerMap* vbucket_map);
const CouchbaseChannelMap& GetChannelMap();
//TODO: Get different hash algorithm if needed.
size_t Hash(const std::string& type,
const butil::StringPiece& key,
const size_t size);
const std::string* GetMaster(const VBucketServerMap* vb_map,
const size_t vb_index, int* index = nullptr);
size_t Hash(const butil::StringPiece& key, const size_t vbuckets_num);
bool UpdateVBucketServerMap(
const std::string* hash_algo,
std::vector<std::string>* vbucket_servers,
const std::vector<std::string>* added_vbuckets,
const std::vector<std::string>* removed_vbuckets);
const int num_replicas,
std::vector<std::vector<int>>& vbucket,
std::vector<std::vector<int>>& fvbucket,
std::vector<std::string>& servers,
const std::vector<std::string>& added_servers,
const std::vector<std::string>& removed_serverss);
static bool Update(VBucketServerMap& vbucket_map,
const ChannelOptions* options,
const std::string* hash_algo,
std::vector<std::string>* vbucket_servers,
const std::vector<std::string>* added_vbuckets,
const std::vector<std::string>* removed_vbuckets);
const int num_replicas,
std::vector<std::vector<int>>& vbucket,
std::vector<std::vector<int>>& fvbucket,
std::vector<std::string>& servers,
const std::vector<std::string>& added_servers,
const std::vector<std::string>& removed_servers);
std::string GetAuthentication() const;
// Options for each memcache channel of vbucket.
ChannelOptions _common_options;
// Listener monitor and update vbucket map information.
std::unique_ptr<CouchbaseServerListener> _listener;
// Memcache channel of each vbucket of couchbase. The key is the server list
// of this vbucket, like 'list://addr1:port1,addr2:port2...'.
butil::DoublyBufferedData<VBucketServerMap> _vbucket_map;
};
......
/*
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
/* cJSON */
/* JSON parser in C. */
/* disable warnings about old C89 functions in MSVC */
#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER)
#define _CRT_SECURE_NO_DEPRECATE
#endif
#ifdef __GNUC__
#pragma GCC visibility push(default)
#endif
#if defined(_MSC_VER)
#pragma warning (push)
/* disable warning about single line comments in system headers */
#pragma warning (disable : 4001)
#endif
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <limits.h>
#include <ctype.h>
#ifdef ENABLE_LOCALES
#include <locale.h>
#endif
#if defined(_MSC_VER)
#pragma warning (pop)
#endif
#ifdef __GNUC__
#pragma GCC visibility pop
#endif
#include "cJSON.h"
/* define our own boolean type */
#define true ((cJSON_bool)1)
#define false ((cJSON_bool)0)
typedef struct {
const unsigned char *json;
size_t position;
} error;
static error global_error = { NULL, 0 };
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void)
{
return (const char*) (global_error.json + global_error.position);
}
CJSON_PUBLIC(char *) cJSON_GetStringValue(cJSON *item) {
if (!cJSON_IsString(item)) {
return NULL;
}
return item->valuestring;
}
/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */
#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 7)
#error cJSON.h and cJSON.c have different versions. Make sure that both have the same.
#endif
CJSON_PUBLIC(const char*) cJSON_Version(void)
{
static char version[15];
sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH);
return version;
}
/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */
static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2)
{
if ((string1 == NULL) || (string2 == NULL))
{
return 1;
}
if (string1 == string2)
{
return 0;
}
for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++)
{
if (*string1 == '\0')
{
return 0;
}
}
return tolower(*string1) - tolower(*string2);
}
typedef struct internal_hooks
{
void *(*allocate)(size_t size);
void (*deallocate)(void *pointer);
void *(*reallocate)(void *pointer, size_t size);
} internal_hooks;
#if defined(_MSC_VER)
/* work around MSVC error C2322: '...' address of dillimport '...' is not static */
static void *internal_malloc(size_t size)
{
return malloc(size);
}
static void internal_free(void *pointer)
{
free(pointer);
}
static void *internal_realloc(void *pointer, size_t size)
{
return realloc(pointer, size);
}
#else
#define internal_malloc malloc
#define internal_free free
#define internal_realloc realloc
#endif
static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc };
static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks)
{
size_t length = 0;
unsigned char *copy = NULL;
if (string == NULL)
{
return NULL;
}
length = strlen((const char*)string) + sizeof("");
copy = (unsigned char*)hooks->allocate(length);
if (copy == NULL)
{
return NULL;
}
memcpy(copy, string, length);
return copy;
}
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks)
{
if (hooks == NULL)
{
/* Reset hooks */
global_hooks.allocate = malloc;
global_hooks.deallocate = free;
global_hooks.reallocate = realloc;
return;
}
global_hooks.allocate = malloc;
if (hooks->malloc_fn != NULL)
{
global_hooks.allocate = hooks->malloc_fn;
}
global_hooks.deallocate = free;
if (hooks->free_fn != NULL)
{
global_hooks.deallocate = hooks->free_fn;
}
/* use realloc only if both free and malloc are used */
global_hooks.reallocate = NULL;
if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free))
{
global_hooks.reallocate = realloc;
}
}
/* Internal constructor. */
static cJSON *cJSON_New_Item(const internal_hooks * const hooks)
{
cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON));
if (node)
{
memset(node, '\0', sizeof(cJSON));
}
return node;
}
/* Delete a cJSON structure. */
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item)
{
cJSON *next = NULL;
while (item != NULL)
{
next = item->next;
if (!(item->type & cJSON_IsReference) && (item->child != NULL))
{
cJSON_Delete(item->child);
}
if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL))
{
global_hooks.deallocate(item->valuestring);
}
if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))
{
global_hooks.deallocate(item->string);
}
global_hooks.deallocate(item);
item = next;
}
}
/* get the decimal point character of the current locale */
static unsigned char get_decimal_point(void)
{
#ifdef ENABLE_LOCALES
struct lconv *lconv = localeconv();
return (unsigned char) lconv->decimal_point[0];
#else
return '.';
#endif
}
typedef struct
{
const unsigned char *content;
size_t length;
size_t offset;
size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */
internal_hooks hooks;
} parse_buffer;
/* check if the given size is left to read in a given parse buffer (starting with 1) */
#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length))
/* check if the buffer can be accessed at the given index (starting with 0) */
#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length))
#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index))
/* get a pointer to the buffer at the position */
#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset)
/* Parse the input text to generate a number, and populate the result into item. */
static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer)
{
double number = 0;
unsigned char *after_end = NULL;
unsigned char number_c_string[64];
unsigned char decimal_point = get_decimal_point();
size_t i = 0;
if ((input_buffer == NULL) || (input_buffer->content == NULL))
{
return false;
}
/* copy the number into a temporary buffer and replace '.' with the decimal point
* of the current locale (for strtod)
* This also takes care of '\0' not necessarily being available for marking the end of the input */
for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++)
{
switch (buffer_at_offset(input_buffer)[i])
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '+':
case '-':
case 'e':
case 'E':
number_c_string[i] = buffer_at_offset(input_buffer)[i];
break;
case '.':
number_c_string[i] = decimal_point;
break;
default:
goto loop_end;
}
}
loop_end:
number_c_string[i] = '\0';
number = strtod((const char*)number_c_string, (char**)&after_end);
if (number_c_string == after_end)
{
return false; /* parse_error */
}
item->valuedouble = number;
/* use saturation in case of overflow */
if (number >= INT_MAX)
{
item->valueint = INT_MAX;
}
else if (number <= INT_MIN)
{
item->valueint = INT_MIN;
}
else
{
item->valueint = (int)number;
}
item->type = cJSON_Number;
input_buffer->offset += (size_t)(after_end - number_c_string);
return true;
}
/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */
CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number)
{
if (number >= INT_MAX)
{
object->valueint = INT_MAX;
}
else if (number <= INT_MIN)
{
object->valueint = INT_MIN;
}
else
{
object->valueint = (int)number;
}
return object->valuedouble = number;
}
typedef struct
{
unsigned char *buffer;
size_t length;
size_t offset;
size_t depth; /* current nesting depth (for formatted printing) */
cJSON_bool noalloc;
cJSON_bool format; /* is this print a formatted print */
internal_hooks hooks;
} printbuffer;
/* realloc printbuffer if necessary to have at least "needed" bytes more */
static unsigned char* ensure(printbuffer * const p, size_t needed)
{
unsigned char *newbuffer = NULL;
size_t newsize = 0;
if ((p == NULL) || (p->buffer == NULL))
{
return NULL;
}
if ((p->length > 0) && (p->offset >= p->length))
{
/* make sure that offset is valid */
return NULL;
}
if (needed > INT_MAX)
{
/* sizes bigger than INT_MAX are currently not supported */
return NULL;
}
needed += p->offset + 1;
if (needed <= p->length)
{
return p->buffer + p->offset;
}
if (p->noalloc) {
return NULL;
}
/* calculate new buffer size */
if (needed > (INT_MAX / 2))
{
/* overflow of int, use INT_MAX if possible */
if (needed <= INT_MAX)
{
newsize = INT_MAX;
}
else
{
return NULL;
}
}
else
{
newsize = needed * 2;
}
if (p->hooks.reallocate != NULL)
{
/* reallocate with realloc if available */
newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize);
if (newbuffer == NULL)
{
p->hooks.deallocate(p->buffer);
p->length = 0;
p->buffer = NULL;
return NULL;
}
}
else
{
/* otherwise reallocate manually */
newbuffer = (unsigned char*)p->hooks.allocate(newsize);
if (!newbuffer)
{
p->hooks.deallocate(p->buffer);
p->length = 0;
p->buffer = NULL;
return NULL;
}
if (newbuffer)
{
memcpy(newbuffer, p->buffer, p->offset + 1);
}
p->hooks.deallocate(p->buffer);
}
p->length = newsize;
p->buffer = newbuffer;
return newbuffer + p->offset;
}
/* calculate the new length of the string in a printbuffer and update the offset */
static void update_offset(printbuffer * const buffer)
{
const unsigned char *buffer_pointer = NULL;
if ((buffer == NULL) || (buffer->buffer == NULL))
{
return;
}
buffer_pointer = buffer->buffer + buffer->offset;
buffer->offset += strlen((const char*)buffer_pointer);
}
/* Render the number nicely from the given item into a string. */
static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer)
{
unsigned char *output_pointer = NULL;
double d = item->valuedouble;
int length = 0;
size_t i = 0;
unsigned char number_buffer[26]; /* temporary buffer to print the number into */
unsigned char decimal_point = get_decimal_point();
double test;
if (output_buffer == NULL)
{
return false;
}
/* This checks for NaN and Infinity */
if ((d * 0) != 0)
{
length = sprintf((char*)number_buffer, "null");
}
else
{
/* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */
length = sprintf((char*)number_buffer, "%1.15g", d);
/* Check whether the original double can be recovered */
if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || ((double)test != d))
{
/* If not, print with 17 decimal places of precision */
length = sprintf((char*)number_buffer, "%1.17g", d);
}
}
/* sprintf failed or buffer overrun occured */
if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1)))
{
return false;
}
/* reserve appropriate space in the output */
output_pointer = ensure(output_buffer, (size_t)length + sizeof(""));
if (output_pointer == NULL)
{
return false;
}
/* copy the printed number to the output and replace locale
* dependent decimal point with '.' */
for (i = 0; i < ((size_t)length); i++)
{
if (number_buffer[i] == decimal_point)
{
output_pointer[i] = '.';
continue;
}
output_pointer[i] = number_buffer[i];
}
output_pointer[i] = '\0';
output_buffer->offset += (size_t)length;
return true;
}
/* parse 4 digit hexadecimal number */
static unsigned parse_hex4(const unsigned char * const input)
{
unsigned int h = 0;
size_t i = 0;
for (i = 0; i < 4; i++)
{
/* parse digit */
if ((input[i] >= '0') && (input[i] <= '9'))
{
h += (unsigned int) input[i] - '0';
}
else if ((input[i] >= 'A') && (input[i] <= 'F'))
{
h += (unsigned int) 10 + input[i] - 'A';
}
else if ((input[i] >= 'a') && (input[i] <= 'f'))
{
h += (unsigned int) 10 + input[i] - 'a';
}
else /* invalid */
{
return 0;
}
if (i < 3)
{
/* shift left to make place for the next nibble */
h = h << 4;
}
}
return h;
}
/* converts a UTF-16 literal to UTF-8
* A literal can be one or two sequences of the form \uXXXX */
static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer)
{
long unsigned int codepoint = 0;
unsigned int first_code = 0;
const unsigned char *first_sequence = input_pointer;
unsigned char utf8_length = 0;
unsigned char utf8_position = 0;
unsigned char sequence_length = 0;
unsigned char first_byte_mark = 0;
if ((input_end - first_sequence) < 6)
{
/* input ends unexpectedly */
goto fail;
}
/* get the first utf16 sequence */
first_code = parse_hex4(first_sequence + 2);
/* check that the code is valid */
if (((first_code >= 0xDC00) && (first_code <= 0xDFFF)))
{
goto fail;
}
/* UTF16 surrogate pair */
if ((first_code >= 0xD800) && (first_code <= 0xDBFF))
{
const unsigned char *second_sequence = first_sequence + 6;
unsigned int second_code = 0;
sequence_length = 12; /* \uXXXX\uXXXX */
if ((input_end - second_sequence) < 6)
{
/* input ends unexpectedly */
goto fail;
}
if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u'))
{
/* missing second half of the surrogate pair */
goto fail;
}
/* get the second utf16 sequence */
second_code = parse_hex4(second_sequence + 2);
/* check that the code is valid */
if ((second_code < 0xDC00) || (second_code > 0xDFFF))
{
/* invalid second half of the surrogate pair */
goto fail;
}
/* calculate the unicode codepoint from the surrogate pair */
codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF));
}
else
{
sequence_length = 6; /* \uXXXX */
codepoint = first_code;
}
/* encode as UTF-8
* takes at maximum 4 bytes to encode:
* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
if (codepoint < 0x80)
{
/* normal ascii, encoding 0xxxxxxx */
utf8_length = 1;
}
else if (codepoint < 0x800)
{
/* two bytes, encoding 110xxxxx 10xxxxxx */
utf8_length = 2;
first_byte_mark = 0xC0; /* 11000000 */
}
else if (codepoint < 0x10000)
{
/* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */
utf8_length = 3;
first_byte_mark = 0xE0; /* 11100000 */
}
else if (codepoint <= 0x10FFFF)
{
/* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */
utf8_length = 4;
first_byte_mark = 0xF0; /* 11110000 */
}
else
{
/* invalid unicode codepoint */
goto fail;
}
/* encode as utf8 */
for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--)
{
/* 10xxxxxx */
(*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF);
codepoint >>= 6;
}
/* encode first byte */
if (utf8_length > 1)
{
(*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF);
}
else
{
(*output_pointer)[0] = (unsigned char)(codepoint & 0x7F);
}
*output_pointer += utf8_length;
return sequence_length;
fail:
return 0;
}
/* Parse the input text into an unescaped cinput, and populate item. */
static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer)
{
const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1;
const unsigned char *input_end = buffer_at_offset(input_buffer) + 1;
unsigned char *output_pointer = NULL;
unsigned char *output = NULL;
/* not a string */
if (buffer_at_offset(input_buffer)[0] != '\"')
{
goto fail;
}
{
/* calculate approximate size of the output (overestimate) */
size_t allocation_length = 0;
size_t skipped_bytes = 0;
while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"'))
{
/* is escape sequence */
if (input_end[0] == '\\')
{
if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length)
{
/* prevent buffer overflow when last input character is a backslash */
goto fail;
}
skipped_bytes++;
input_end++;
}
input_end++;
}
if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"'))
{
goto fail; /* string ended unexpectedly */
}
/* This is at most how much we need for the output */
allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes;
output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof(""));
if (output == NULL)
{
goto fail; /* allocation failure */
}
}
output_pointer = output;
/* loop through the string literal */
while (input_pointer < input_end)
{
if (*input_pointer != '\\')
{
*output_pointer++ = *input_pointer++;
}
/* escape sequence */
else
{
unsigned char sequence_length = 2;
if ((input_end - input_pointer) < 1)
{
goto fail;
}
switch (input_pointer[1])
{
case 'b':
*output_pointer++ = '\b';
break;
case 'f':
*output_pointer++ = '\f';
break;
case 'n':
*output_pointer++ = '\n';
break;
case 'r':
*output_pointer++ = '\r';
break;
case 't':
*output_pointer++ = '\t';
break;
case '\"':
case '\\':
case '/':
*output_pointer++ = input_pointer[1];
break;
/* UTF-16 literal */
case 'u':
sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer);
if (sequence_length == 0)
{
/* failed to convert UTF16-literal to UTF-8 */
goto fail;
}
break;
default:
goto fail;
}
input_pointer += sequence_length;
}
}
/* zero terminate the output */
*output_pointer = '\0';
item->type = cJSON_String;
item->valuestring = (char*)output;
input_buffer->offset = (size_t) (input_end - input_buffer->content);
input_buffer->offset++;
return true;
fail:
if (output != NULL)
{
input_buffer->hooks.deallocate(output);
}
if (input_pointer != NULL)
{
input_buffer->offset = (size_t)(input_pointer - input_buffer->content);
}
return false;
}
/* Render the cstring provided to an escaped version that can be printed. */
static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer)
{
const unsigned char *input_pointer = NULL;
unsigned char *output = NULL;
unsigned char *output_pointer = NULL;
size_t output_length = 0;
/* numbers of additional characters needed for escaping */
size_t escape_characters = 0;
if (output_buffer == NULL)
{
return false;
}
/* empty string */
if (input == NULL)
{
output = ensure(output_buffer, sizeof("\"\""));
if (output == NULL)
{
return false;
}
strcpy((char*)output, "\"\"");
return true;
}
/* set "flag" to 1 if something needs to be escaped */
for (input_pointer = input; *input_pointer; input_pointer++)
{
switch (*input_pointer)
{
case '\"':
case '\\':
case '\b':
case '\f':
case '\n':
case '\r':
case '\t':
/* one character escape sequence */
escape_characters++;
break;
default:
if (*input_pointer < 32)
{
/* UTF-16 escape sequence uXXXX */
escape_characters += 5;
}
break;
}
}
output_length = (size_t)(input_pointer - input) + escape_characters;
output = ensure(output_buffer, output_length + sizeof("\"\""));
if (output == NULL)
{
return false;
}
/* no characters have to be escaped */
if (escape_characters == 0)
{
output[0] = '\"';
memcpy(output + 1, input, output_length);
output[output_length + 1] = '\"';
output[output_length + 2] = '\0';
return true;
}
output[0] = '\"';
output_pointer = output + 1;
/* copy the string */
for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++)
{
if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\'))
{
/* normal character, copy */
*output_pointer = *input_pointer;
}
else
{
/* character needs to be escaped */
*output_pointer++ = '\\';
switch (*input_pointer)
{
case '\\':
*output_pointer = '\\';
break;
case '\"':
*output_pointer = '\"';
break;
case '\b':
*output_pointer = 'b';
break;
case '\f':
*output_pointer = 'f';
break;
case '\n':
*output_pointer = 'n';
break;
case '\r':
*output_pointer = 'r';
break;
case '\t':
*output_pointer = 't';
break;
default:
/* escape and print as unicode codepoint */
sprintf((char*)output_pointer, "u%04x", *input_pointer);
output_pointer += 4;
break;
}
}
}
output[output_length + 1] = '\"';
output[output_length + 2] = '\0';
return true;
}
/* Invoke print_string_ptr (which is useful) on an item. */
static cJSON_bool print_string(const cJSON * const item, printbuffer * const p)
{
return print_string_ptr((unsigned char*)item->valuestring, p);
}
/* Predeclare these prototypes. */
static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer);
static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer);
static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer);
static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer);
static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer);
static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer);
/* Utility to jump whitespace and cr/lf */
static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer)
{
if ((buffer == NULL) || (buffer->content == NULL))
{
return NULL;
}
while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32))
{
buffer->offset++;
}
if (buffer->offset == buffer->length)
{
buffer->offset--;
}
return buffer;
}
/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */
static parse_buffer *skip_utf8_bom(parse_buffer * const buffer)
{
if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0))
{
return NULL;
}
if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0))
{
buffer->offset += 3;
}
return buffer;
}
/* Parse an object - create a new root, and populate. */
CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated)
{
parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } };
cJSON *item = NULL;
/* reset error position */
global_error.json = NULL;
global_error.position = 0;
if (value == NULL)
{
goto fail;
}
buffer.content = (const unsigned char*)value;
buffer.length = strlen((const char*)value) + sizeof("");
buffer.offset = 0;
buffer.hooks = global_hooks;
item = cJSON_New_Item(&global_hooks);
if (item == NULL) /* memory fail */
{
goto fail;
}
if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer))))
{
/* parse failure. ep is set. */
goto fail;
}
/* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */
if (require_null_terminated)
{
buffer_skip_whitespace(&buffer);
if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0')
{
goto fail;
}
}
if (return_parse_end)
{
*return_parse_end = (const char*)buffer_at_offset(&buffer);
}
return item;
fail:
if (item != NULL)
{
cJSON_Delete(item);
}
if (value != NULL)
{
error local_error;
local_error.json = (const unsigned char*)value;
local_error.position = 0;
if (buffer.offset < buffer.length)
{
local_error.position = buffer.offset;
}
else if (buffer.length > 0)
{
local_error.position = buffer.length - 1;
}
if (return_parse_end != NULL)
{
*return_parse_end = (const char*)local_error.json + local_error.position;
}
global_error = local_error;
}
return NULL;
}
/* Default options for cJSON_Parse */
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value)
{
return cJSON_ParseWithOpts(value, 0, 0);
}
#define cjson_min(a, b) ((a < b) ? a : b)
static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks)
{
static const size_t default_buffer_size = 256;
printbuffer buffer[1];
unsigned char *printed = NULL;
memset(buffer, 0, sizeof(buffer));
/* create buffer */
buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size);
buffer->length = default_buffer_size;
buffer->format = format;
buffer->hooks = *hooks;
if (buffer->buffer == NULL)
{
goto fail;
}
/* print the value */
if (!print_value(item, buffer))
{
goto fail;
}
update_offset(buffer);
/* check if reallocate is available */
if (hooks->reallocate != NULL)
{
printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1);
if (printed == NULL) {
goto fail;
}
buffer->buffer = NULL;
}
else /* otherwise copy the JSON over to a new buffer */
{
printed = (unsigned char*) hooks->allocate(buffer->offset + 1);
if (printed == NULL)
{
goto fail;
}
memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1));
printed[buffer->offset] = '\0'; /* just to be sure */
/* free the buffer */
hooks->deallocate(buffer->buffer);
}
return printed;
fail:
if (buffer->buffer != NULL)
{
hooks->deallocate(buffer->buffer);
}
if (printed != NULL)
{
hooks->deallocate(printed);
}
return NULL;
}
/* Render a cJSON item/entity/structure to text. */
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item)
{
return (char*)print(item, true, &global_hooks);
}
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item)
{
return (char*)print(item, false, &global_hooks);
}
CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt)
{
printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } };
if (prebuffer < 0)
{
return NULL;
}
p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer);
if (!p.buffer)
{
return NULL;
}
p.length = (size_t)prebuffer;
p.offset = 0;
p.noalloc = false;
p.format = fmt;
p.hooks = global_hooks;
if (!print_value(item, &p))
{
global_hooks.deallocate(p.buffer);
return NULL;
}
return (char*)p.buffer;
}
CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buf, const int len, const cJSON_bool fmt)
{
printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } };
if ((len < 0) || (buf == NULL))
{
return false;
}
p.buffer = (unsigned char*)buf;
p.length = (size_t)len;
p.offset = 0;
p.noalloc = true;
p.format = fmt;
p.hooks = global_hooks;
return print_value(item, &p);
}
/* Parser core - when encountering text, process appropriately. */
static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer)
{
if ((input_buffer == NULL) || (input_buffer->content == NULL))
{
return false; /* no input */
}
/* parse the different types of values */
/* null */
if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0))
{
item->type = cJSON_NULL;
input_buffer->offset += 4;
return true;
}
/* false */
if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0))
{
item->type = cJSON_False;
input_buffer->offset += 5;
return true;
}
/* true */
if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0))
{
item->type = cJSON_True;
item->valueint = 1;
input_buffer->offset += 4;
return true;
}
/* string */
if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"'))
{
return parse_string(item, input_buffer);
}
/* number */
if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9'))))
{
return parse_number(item, input_buffer);
}
/* array */
if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '['))
{
return parse_array(item, input_buffer);
}
/* object */
if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{'))
{
return parse_object(item, input_buffer);
}
return false;
}
/* Render a value to text. */
static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer)
{
unsigned char *output = NULL;
if ((item == NULL) || (output_buffer == NULL))
{
return false;
}
switch ((item->type) & 0xFF)
{
case cJSON_NULL:
output = ensure(output_buffer, 5);
if (output == NULL)
{
return false;
}
strcpy((char*)output, "null");
return true;
case cJSON_False:
output = ensure(output_buffer, 6);
if (output == NULL)
{
return false;
}
strcpy((char*)output, "false");
return true;
case cJSON_True:
output = ensure(output_buffer, 5);
if (output == NULL)
{
return false;
}
strcpy((char*)output, "true");
return true;
case cJSON_Number:
return print_number(item, output_buffer);
case cJSON_Raw:
{
size_t raw_length = 0;
if (item->valuestring == NULL)
{
return false;
}
raw_length = strlen(item->valuestring) + sizeof("");
output = ensure(output_buffer, raw_length);
if (output == NULL)
{
return false;
}
memcpy(output, item->valuestring, raw_length);
return true;
}
case cJSON_String:
return print_string(item, output_buffer);
case cJSON_Array:
return print_array(item, output_buffer);
case cJSON_Object:
return print_object(item, output_buffer);
default:
return false;
}
}
/* Build an array from input text. */
static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer)
{
cJSON *head = NULL; /* head of the linked list */
cJSON *current_item = NULL;
if (input_buffer->depth >= CJSON_NESTING_LIMIT)
{
return false; /* to deeply nested */
}
input_buffer->depth++;
if (buffer_at_offset(input_buffer)[0] != '[')
{
/* not an array */
goto fail;
}
input_buffer->offset++;
buffer_skip_whitespace(input_buffer);
if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']'))
{
/* empty array */
goto success;
}
/* check if we skipped to the end of the buffer */
if (cannot_access_at_index(input_buffer, 0))
{
input_buffer->offset--;
goto fail;
}
/* step back to character in front of the first element */
input_buffer->offset--;
/* loop through the comma separated array elements */
do
{
/* allocate next item */
cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks));
if (new_item == NULL)
{
goto fail; /* allocation failure */
}
/* attach next item to list */
if (head == NULL)
{
/* start the linked list */
current_item = head = new_item;
}
else
{
/* add to the end and advance */
current_item->next = new_item;
new_item->prev = current_item;
current_item = new_item;
}
/* parse next value */
input_buffer->offset++;
buffer_skip_whitespace(input_buffer);
if (!parse_value(current_item, input_buffer))
{
goto fail; /* failed to parse value */
}
buffer_skip_whitespace(input_buffer);
}
while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ','));
if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']')
{
goto fail; /* expected end of array */
}
success:
input_buffer->depth--;
item->type = cJSON_Array;
item->child = head;
input_buffer->offset++;
return true;
fail:
if (head != NULL)
{
cJSON_Delete(head);
}
return false;
}
/* Render an array to text */
static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer)
{
unsigned char *output_pointer = NULL;
size_t length = 0;
cJSON *current_element = item->child;
if (output_buffer == NULL)
{
return false;
}
/* Compose the output array. */
/* opening square bracket */
output_pointer = ensure(output_buffer, 1);
if (output_pointer == NULL)
{
return false;
}
*output_pointer = '[';
output_buffer->offset++;
output_buffer->depth++;
while (current_element != NULL)
{
if (!print_value(current_element, output_buffer))
{
return false;
}
update_offset(output_buffer);
if (current_element->next)
{
length = (size_t) (output_buffer->format ? 2 : 1);
output_pointer = ensure(output_buffer, length + 1);
if (output_pointer == NULL)
{
return false;
}
*output_pointer++ = ',';
if(output_buffer->format)
{
*output_pointer++ = ' ';
}
*output_pointer = '\0';
output_buffer->offset += length;
}
current_element = current_element->next;
}
output_pointer = ensure(output_buffer, 2);
if (output_pointer == NULL)
{
return false;
}
*output_pointer++ = ']';
*output_pointer = '\0';
output_buffer->depth--;
return true;
}
/* Build an object from the text. */
static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer)
{
cJSON *head = NULL; /* linked list head */
cJSON *current_item = NULL;
if (input_buffer->depth >= CJSON_NESTING_LIMIT)
{
return false; /* to deeply nested */
}
input_buffer->depth++;
if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{'))
{
goto fail; /* not an object */
}
input_buffer->offset++;
buffer_skip_whitespace(input_buffer);
if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}'))
{
goto success; /* empty object */
}
/* check if we skipped to the end of the buffer */
if (cannot_access_at_index(input_buffer, 0))
{
input_buffer->offset--;
goto fail;
}
/* step back to character in front of the first element */
input_buffer->offset--;
/* loop through the comma separated array elements */
do
{
/* allocate next item */
cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks));
if (new_item == NULL)
{
goto fail; /* allocation failure */
}
/* attach next item to list */
if (head == NULL)
{
/* start the linked list */
current_item = head = new_item;
}
else
{
/* add to the end and advance */
current_item->next = new_item;
new_item->prev = current_item;
current_item = new_item;
}
/* parse the name of the child */
input_buffer->offset++;
buffer_skip_whitespace(input_buffer);
if (!parse_string(current_item, input_buffer))
{
goto fail; /* faile to parse name */
}
buffer_skip_whitespace(input_buffer);
/* swap valuestring and string, because we parsed the name */
current_item->string = current_item->valuestring;
current_item->valuestring = NULL;
if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':'))
{
goto fail; /* invalid object */
}
/* parse the value */
input_buffer->offset++;
buffer_skip_whitespace(input_buffer);
if (!parse_value(current_item, input_buffer))
{
goto fail; /* failed to parse value */
}
buffer_skip_whitespace(input_buffer);
}
while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ','));
if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}'))
{
goto fail; /* expected end of object */
}
success:
input_buffer->depth--;
item->type = cJSON_Object;
item->child = head;
input_buffer->offset++;
return true;
fail:
if (head != NULL)
{
cJSON_Delete(head);
}
return false;
}
/* Render an object to text. */
static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer)
{
unsigned char *output_pointer = NULL;
size_t length = 0;
cJSON *current_item = item->child;
if (output_buffer == NULL)
{
return false;
}
/* Compose the output: */
length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */
output_pointer = ensure(output_buffer, length + 1);
if (output_pointer == NULL)
{
return false;
}
*output_pointer++ = '{';
output_buffer->depth++;
if (output_buffer->format)
{
*output_pointer++ = '\n';
}
output_buffer->offset += length;
while (current_item)
{
if (output_buffer->format)
{
size_t i;
output_pointer = ensure(output_buffer, output_buffer->depth);
if (output_pointer == NULL)
{
return false;
}
for (i = 0; i < output_buffer->depth; i++)
{
*output_pointer++ = '\t';
}
output_buffer->offset += output_buffer->depth;
}
/* print key */
if (!print_string_ptr((unsigned char*)current_item->string, output_buffer))
{
return false;
}
update_offset(output_buffer);
length = (size_t) (output_buffer->format ? 2 : 1);
output_pointer = ensure(output_buffer, length);
if (output_pointer == NULL)
{
return false;
}
*output_pointer++ = ':';
if (output_buffer->format)
{
*output_pointer++ = '\t';
}
output_buffer->offset += length;
/* print value */
if (!print_value(current_item, output_buffer))
{
return false;
}
update_offset(output_buffer);
/* print comma if not last */
length = (size_t) ((output_buffer->format ? 1 : 0) + (current_item->next ? 1 : 0));
output_pointer = ensure(output_buffer, length + 1);
if (output_pointer == NULL)
{
return false;
}
if (current_item->next)
{
*output_pointer++ = ',';
}
if (output_buffer->format)
{
*output_pointer++ = '\n';
}
*output_pointer = '\0';
output_buffer->offset += length;
current_item = current_item->next;
}
output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2);
if (output_pointer == NULL)
{
return false;
}
if (output_buffer->format)
{
size_t i;
for (i = 0; i < (output_buffer->depth - 1); i++)
{
*output_pointer++ = '\t';
}
}
*output_pointer++ = '}';
*output_pointer = '\0';
output_buffer->depth--;
return true;
}
/* Get Array size/item / object item. */
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array)
{
cJSON *child = NULL;
size_t size = 0;
if (array == NULL)
{
return 0;
}
child = array->child;
while(child != NULL)
{
size++;
child = child->next;
}
/* FIXME: Can overflow here. Cannot be fixed without breaking the API */
return (int)size;
}
static cJSON* get_array_item(const cJSON *array, size_t index)
{
cJSON *current_child = NULL;
if (array == NULL)
{
return NULL;
}
current_child = array->child;
while ((current_child != NULL) && (index > 0))
{
index--;
current_child = current_child->next;
}
return current_child;
}
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index)
{
if (index < 0)
{
return NULL;
}
return get_array_item(array, (size_t)index);
}
static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive)
{
cJSON *current_element = NULL;
if ((object == NULL) || (name == NULL))
{
return NULL;
}
current_element = object->child;
if (case_sensitive)
{
while ((current_element != NULL) && (strcmp(name, current_element->string) != 0))
{
current_element = current_element->next;
}
}
else
{
while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0))
{
current_element = current_element->next;
}
}
return current_element;
}
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string)
{
return get_object_item(object, string, false);
}
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string)
{
return get_object_item(object, string, true);
}
CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string)
{
return cJSON_GetObjectItem(object, string) ? 1 : 0;
}
/* Utility for array list handling. */
static void suffix_object(cJSON *prev, cJSON *item)
{
prev->next = item;
item->prev = prev;
}
/* Utility for handling references. */
static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks)
{
cJSON *reference = NULL;
if (item == NULL)
{
return NULL;
}
reference = cJSON_New_Item(hooks);
if (reference == NULL)
{
return NULL;
}
memcpy(reference, item, sizeof(cJSON));
reference->string = NULL;
reference->type |= cJSON_IsReference;
reference->next = reference->prev = NULL;
return reference;
}
static cJSON_bool add_item_to_array(cJSON *array, cJSON *item)
{
cJSON *child = NULL;
if ((item == NULL) || (array == NULL))
{
return false;
}
child = array->child;
if (child == NULL)
{
/* list is empty, start new one */
array->child = item;
}
else
{
/* append to the end */
while (child->next)
{
child = child->next;
}
suffix_object(child, item);
}
return true;
}
/* Add item to array/object. */
CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item)
{
add_item_to_array(array, item);
}
#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5))))
#pragma GCC diagnostic push
#endif
#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
/* helper function to cast away const */
static void* cast_away_const(const void* string)
{
return (void*)string;
}
#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5))))
#pragma GCC diagnostic pop
#endif
static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key)
{
char *new_key = NULL;
int new_type = cJSON_Invalid;
if ((object == NULL) || (string == NULL) || (item == NULL))
{
return false;
}
if (constant_key)
{
new_key = (char*)cast_away_const(string);
new_type = item->type | cJSON_StringIsConst;
}
else
{
new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks);
if (new_key == NULL)
{
return false;
}
new_type = item->type & ~cJSON_StringIsConst;
}
if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))
{
hooks->deallocate(item->string);
}
item->string = new_key;
item->type = new_type;
return add_item_to_array(object, item);
}
CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item)
{
add_item_to_object(object, string, item, &global_hooks, false);
}
/* Add an item to an object with constant string as key */
CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item)
{
add_item_to_object(object, string, item, &global_hooks, true);
}
CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item)
{
if (array == NULL)
{
return;
}
add_item_to_array(array, create_reference(item, &global_hooks));
}
CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item)
{
if ((object == NULL) || (string == NULL))
{
return;
}
add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false);
}
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name)
{
cJSON *null = cJSON_CreateNull();
if (add_item_to_object(object, name, null, &global_hooks, false))
{
return null;
}
cJSON_Delete(null);
return NULL;
}
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name)
{
cJSON *true_item = cJSON_CreateTrue();
if (add_item_to_object(object, name, true_item, &global_hooks, false))
{
return true_item;
}
cJSON_Delete(true_item);
return NULL;
}
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name)
{
cJSON *false_item = cJSON_CreateFalse();
if (add_item_to_object(object, name, false_item, &global_hooks, false))
{
return false_item;
}
cJSON_Delete(false_item);
return NULL;
}
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean)
{
cJSON *bool_item = cJSON_CreateBool(boolean);
if (add_item_to_object(object, name, bool_item, &global_hooks, false))
{
return bool_item;
}
cJSON_Delete(bool_item);
return NULL;
}
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number)
{
cJSON *number_item = cJSON_CreateNumber(number);
if (add_item_to_object(object, name, number_item, &global_hooks, false))
{
return number_item;
}
cJSON_Delete(number_item);
return NULL;
}
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string)
{
cJSON *string_item = cJSON_CreateString(string);
if (add_item_to_object(object, name, string_item, &global_hooks, false))
{
return string_item;
}
cJSON_Delete(string_item);
return NULL;
}
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw)
{
cJSON *raw_item = cJSON_CreateRaw(raw);
if (add_item_to_object(object, name, raw_item, &global_hooks, false))
{
return raw_item;
}
cJSON_Delete(raw_item);
return NULL;
}
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name)
{
cJSON *object_item = cJSON_CreateObject();
if (add_item_to_object(object, name, object_item, &global_hooks, false))
{
return object_item;
}
cJSON_Delete(object_item);
return NULL;
}
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name)
{
cJSON *array = cJSON_CreateArray();
if (add_item_to_object(object, name, array, &global_hooks, false))
{
return array;
}
cJSON_Delete(array);
return NULL;
}
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item)
{
if ((parent == NULL) || (item == NULL))
{
return NULL;
}
if (item->prev != NULL)
{
/* not the first element */
item->prev->next = item->next;
}
if (item->next != NULL)
{
/* not the last element */
item->next->prev = item->prev;
}
if (item == parent->child)
{
/* first element */
parent->child = item->next;
}
/* make sure the detached item doesn't point anywhere anymore */
item->prev = NULL;
item->next = NULL;
return item;
}
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which)
{
if (which < 0)
{
return NULL;
}
return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which));
}
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which)
{
cJSON_Delete(cJSON_DetachItemFromArray(array, which));
}
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string)
{
cJSON *to_detach = cJSON_GetObjectItem(object, string);
return cJSON_DetachItemViaPointer(object, to_detach);
}
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string)
{
cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string);
return cJSON_DetachItemViaPointer(object, to_detach);
}
CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string)
{
cJSON_Delete(cJSON_DetachItemFromObject(object, string));
}
CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string)
{
cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string));
}
/* Replace array/object items with new ones. */
CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem)
{
cJSON *after_inserted = NULL;
if (which < 0)
{
return;
}
after_inserted = get_array_item(array, (size_t)which);
if (after_inserted == NULL)
{
add_item_to_array(array, newitem);
return;
}
newitem->next = after_inserted;
newitem->prev = after_inserted->prev;
after_inserted->prev = newitem;
if (after_inserted == array->child)
{
array->child = newitem;
}
else
{
newitem->prev->next = newitem;
}
}
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement)
{
if ((parent == NULL) || (replacement == NULL) || (item == NULL))
{
return false;
}
if (replacement == item)
{
return true;
}
replacement->next = item->next;
replacement->prev = item->prev;
if (replacement->next != NULL)
{
replacement->next->prev = replacement;
}
if (replacement->prev != NULL)
{
replacement->prev->next = replacement;
}
if (parent->child == item)
{
parent->child = replacement;
}
item->next = NULL;
item->prev = NULL;
cJSON_Delete(item);
return true;
}
CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem)
{
if (which < 0)
{
return;
}
cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem);
}
static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive)
{
if ((replacement == NULL) || (string == NULL))
{
return false;
}
/* replace the name in the replacement */
if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL))
{
cJSON_free(replacement->string);
}
replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks);
replacement->type &= ~cJSON_StringIsConst;
cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement);
return true;
}
CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem)
{
replace_item_in_object(object, string, newitem, false);
}
CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem)
{
replace_item_in_object(object, string, newitem, true);
}
/* Create basic types: */
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void)
{
cJSON *item = cJSON_New_Item(&global_hooks);
if(item)
{
item->type = cJSON_NULL;
}
return item;
}
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void)
{
cJSON *item = cJSON_New_Item(&global_hooks);
if(item)
{
item->type = cJSON_True;
}
return item;
}
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void)
{
cJSON *item = cJSON_New_Item(&global_hooks);
if(item)
{
item->type = cJSON_False;
}
return item;
}
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool b)
{
cJSON *item = cJSON_New_Item(&global_hooks);
if(item)
{
item->type = b ? cJSON_True : cJSON_False;
}
return item;
}
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num)
{
cJSON *item = cJSON_New_Item(&global_hooks);
if(item)
{
item->type = cJSON_Number;
item->valuedouble = num;
/* use saturation in case of overflow */
if (num >= INT_MAX)
{
item->valueint = INT_MAX;
}
else if (num <= INT_MIN)
{
item->valueint = INT_MIN;
}
else
{
item->valueint = (int)num;
}
}
return item;
}
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string)
{
cJSON *item = cJSON_New_Item(&global_hooks);
if(item)
{
item->type = cJSON_String;
item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks);
if(!item->valuestring)
{
cJSON_Delete(item);
return NULL;
}
}
return item;
}
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string)
{
cJSON *item = cJSON_New_Item(&global_hooks);
if (item != NULL)
{
item->type = cJSON_String | cJSON_IsReference;
item->valuestring = (char*)cast_away_const(string);
}
return item;
}
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child)
{
cJSON *item = cJSON_New_Item(&global_hooks);
if (item != NULL) {
item->type = cJSON_Object | cJSON_IsReference;
item->child = (cJSON*)cast_away_const(child);
}
return item;
}
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) {
cJSON *item = cJSON_New_Item(&global_hooks);
if (item != NULL) {
item->type = cJSON_Array | cJSON_IsReference;
item->child = (cJSON*)cast_away_const(child);
}
return item;
}
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw)
{
cJSON *item = cJSON_New_Item(&global_hooks);
if(item)
{
item->type = cJSON_Raw;
item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks);
if(!item->valuestring)
{
cJSON_Delete(item);
return NULL;
}
}
return item;
}
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void)
{
cJSON *item = cJSON_New_Item(&global_hooks);
if(item)
{
item->type=cJSON_Array;
}
return item;
}
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void)
{
cJSON *item = cJSON_New_Item(&global_hooks);
if (item)
{
item->type = cJSON_Object;
}
return item;
}
/* Create Arrays: */
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count)
{
size_t i = 0;
cJSON *n = NULL;
cJSON *p = NULL;
cJSON *a = NULL;
if ((count < 0) || (numbers == NULL))
{
return NULL;
}
a = cJSON_CreateArray();
for(i = 0; a && (i < (size_t)count); i++)
{
n = cJSON_CreateNumber(numbers[i]);
if (!n)
{
cJSON_Delete(a);
return NULL;
}
if(!i)
{
a->child = n;
}
else
{
suffix_object(p, n);
}
p = n;
}
return a;
}
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count)
{
size_t i = 0;
cJSON *n = NULL;
cJSON *p = NULL;
cJSON *a = NULL;
if ((count < 0) || (numbers == NULL))
{
return NULL;
}
a = cJSON_CreateArray();
for(i = 0; a && (i < (size_t)count); i++)
{
n = cJSON_CreateNumber((double)numbers[i]);
if(!n)
{
cJSON_Delete(a);
return NULL;
}
if(!i)
{
a->child = n;
}
else
{
suffix_object(p, n);
}
p = n;
}
return a;
}
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count)
{
size_t i = 0;
cJSON *n = NULL;
cJSON *p = NULL;
cJSON *a = NULL;
if ((count < 0) || (numbers == NULL))
{
return NULL;
}
a = cJSON_CreateArray();
for(i = 0;a && (i < (size_t)count); i++)
{
n = cJSON_CreateNumber(numbers[i]);
if(!n)
{
cJSON_Delete(a);
return NULL;
}
if(!i)
{
a->child = n;
}
else
{
suffix_object(p, n);
}
p = n;
}
return a;
}
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count)
{
size_t i = 0;
cJSON *n = NULL;
cJSON *p = NULL;
cJSON *a = NULL;
if ((count < 0) || (strings == NULL))
{
return NULL;
}
a = cJSON_CreateArray();
for (i = 0; a && (i < (size_t)count); i++)
{
n = cJSON_CreateString(strings[i]);
if(!n)
{
cJSON_Delete(a);
return NULL;
}
if(!i)
{
a->child = n;
}
else
{
suffix_object(p,n);
}
p = n;
}
return a;
}
/* Duplication */
CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse)
{
cJSON *newitem = NULL;
cJSON *child = NULL;
cJSON *next = NULL;
cJSON *newchild = NULL;
/* Bail on bad ptr */
if (!item)
{
goto fail;
}
/* Create new item */
newitem = cJSON_New_Item(&global_hooks);
if (!newitem)
{
goto fail;
}
/* Copy over all vars */
newitem->type = item->type & (~cJSON_IsReference);
newitem->valueint = item->valueint;
newitem->valuedouble = item->valuedouble;
if (item->valuestring)
{
newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks);
if (!newitem->valuestring)
{
goto fail;
}
}
if (item->string)
{
newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks);
if (!newitem->string)
{
goto fail;
}
}
/* If non-recursive, then we're done! */
if (!recurse)
{
return newitem;
}
/* Walk the ->next chain for the child. */
child = item->child;
while (child != NULL)
{
newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */
if (!newchild)
{
goto fail;
}
if (next != NULL)
{
/* If newitem->child already set, then crosswire ->prev and ->next and move on */
next->next = newchild;
newchild->prev = next;
next = newchild;
}
else
{
/* Set newitem->child and move to it */
newitem->child = newchild;
next = newchild;
}
child = child->next;
}
return newitem;
fail:
if (newitem != NULL)
{
cJSON_Delete(newitem);
}
return NULL;
}
CJSON_PUBLIC(void) cJSON_Minify(char *json)
{
unsigned char *into = (unsigned char*)json;
if (json == NULL)
{
return;
}
while (*json)
{
if (*json == ' ')
{
json++;
}
else if (*json == '\t')
{
/* Whitespace characters. */
json++;
}
else if (*json == '\r')
{
json++;
}
else if (*json=='\n')
{
json++;
}
else if ((*json == '/') && (json[1] == '/'))
{
/* double-slash comments, to end of line. */
while (*json && (*json != '\n'))
{
json++;
}
}
else if ((*json == '/') && (json[1] == '*'))
{
/* multiline comments. */
while (*json && !((*json == '*') && (json[1] == '/')))
{
json++;
}
json += 2;
}
else if (*json == '\"')
{
/* string literals, which are \" sensitive. */
*into++ = (unsigned char)*json++;
while (*json && (*json != '\"'))
{
if (*json == '\\')
{
*into++ = (unsigned char)*json++;
}
*into++ = (unsigned char)*json++;
}
*into++ = (unsigned char)*json++;
}
else
{
/* All other characters. */
*into++ = (unsigned char)*json++;
}
}
/* and null-terminate. */
*into = '\0';
}
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item)
{
if (item == NULL)
{
return false;
}
return (item->type & 0xFF) == cJSON_Invalid;
}
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item)
{
if (item == NULL)
{
return false;
}
return (item->type & 0xFF) == cJSON_False;
}
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item)
{
if (item == NULL)
{
return false;
}
return (item->type & 0xff) == cJSON_True;
}
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item)
{
if (item == NULL)
{
return false;
}
return (item->type & (cJSON_True | cJSON_False)) != 0;
}
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item)
{
if (item == NULL)
{
return false;
}
return (item->type & 0xFF) == cJSON_NULL;
}
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item)
{
if (item == NULL)
{
return false;
}
return (item->type & 0xFF) == cJSON_Number;
}
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item)
{
if (item == NULL)
{
return false;
}
return (item->type & 0xFF) == cJSON_String;
}
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item)
{
if (item == NULL)
{
return false;
}
return (item->type & 0xFF) == cJSON_Array;
}
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item)
{
if (item == NULL)
{
return false;
}
return (item->type & 0xFF) == cJSON_Object;
}
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item)
{
if (item == NULL)
{
return false;
}
return (item->type & 0xFF) == cJSON_Raw;
}
CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive)
{
if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)) || cJSON_IsInvalid(a))
{
return false;
}
/* check if type is valid */
switch (a->type & 0xFF)
{
case cJSON_False:
case cJSON_True:
case cJSON_NULL:
case cJSON_Number:
case cJSON_String:
case cJSON_Raw:
case cJSON_Array:
case cJSON_Object:
break;
default:
return false;
}
/* identical objects are equal */
if (a == b)
{
return true;
}
switch (a->type & 0xFF)
{
/* in these cases and equal type is enough */
case cJSON_False:
case cJSON_True:
case cJSON_NULL:
return true;
case cJSON_Number:
if (a->valuedouble == b->valuedouble)
{
return true;
}
return false;
case cJSON_String:
case cJSON_Raw:
if ((a->valuestring == NULL) || (b->valuestring == NULL))
{
return false;
}
if (strcmp(a->valuestring, b->valuestring) == 0)
{
return true;
}
return false;
case cJSON_Array:
{
cJSON *a_element = a->child;
cJSON *b_element = b->child;
for (; (a_element != NULL) && (b_element != NULL);)
{
if (!cJSON_Compare(a_element, b_element, case_sensitive))
{
return false;
}
a_element = a_element->next;
b_element = b_element->next;
}
/* one of the arrays is longer than the other */
if (a_element != b_element) {
return false;
}
return true;
}
case cJSON_Object:
{
cJSON *a_element = NULL;
cJSON *b_element = NULL;
cJSON_ArrayForEach(a_element, a)
{
/* TODO This has O(n^2) runtime, which is horrible! */
b_element = get_object_item(b, a_element->string, case_sensitive);
if (b_element == NULL)
{
return false;
}
if (!cJSON_Compare(a_element, b_element, case_sensitive))
{
return false;
}
}
/* doing this twice, once on a and b to prevent true comparison if a subset of b
* TODO: Do this the proper way, this is just a fix for now */
cJSON_ArrayForEach(b_element, b)
{
a_element = get_object_item(a, b_element->string, case_sensitive);
if (a_element == NULL)
{
return false;
}
if (!cJSON_Compare(b_element, a_element, case_sensitive))
{
return false;
}
}
return true;
}
default:
return false;
}
}
CJSON_PUBLIC(void *) cJSON_malloc(size_t size)
{
return global_hooks.allocate(size);
}
CJSON_PUBLIC(void) cJSON_free(void *object)
{
global_hooks.deallocate(object);
}
/*
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef cJSON__h
#define cJSON__h
/* project version */
#define CJSON_VERSION_MAJOR 1
#define CJSON_VERSION_MINOR 7
#define CJSON_VERSION_PATCH 7
#include <stddef.h>
/* cJSON Types: */
#define cJSON_Invalid (0)
#define cJSON_False (1 << 0)
#define cJSON_True (1 << 1)
#define cJSON_NULL (1 << 2)
#define cJSON_Number (1 << 3)
#define cJSON_String (1 << 4)
#define cJSON_Array (1 << 5)
#define cJSON_Object (1 << 6)
#define cJSON_Raw (1 << 7) /* raw json */
#define cJSON_IsReference 256
#define cJSON_StringIsConst 512
/* The cJSON structure: */
typedef struct cJSON
{
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *next;
struct cJSON *prev;
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
struct cJSON *child;
/* The type of the item, as above. */
int type;
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
char *valuestring;
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
int valueint;
/* The item's number, if type==cJSON_Number */
double valuedouble;
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
char *string;
} cJSON;
typedef struct cJSON_Hooks
{
void *(*malloc_fn)(size_t sz);
void (*free_fn)(void *ptr);
} cJSON_Hooks;
typedef int cJSON_bool;
#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
#define __WINDOWS__
#endif
#ifdef __WINDOWS__
/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 2 define options:
CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
For *nix builds that support visibility attribute, you can define similar behavior by
setting default visibility to hidden by adding
-fvisibility=hidden (for gcc)
or
-xldscope=hidden (for sun cc)
to CFLAGS
then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
*/
/* export symbols by default, this is necessary for copy pasting the C and header file */
#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_EXPORT_SYMBOLS
#endif
#if defined(CJSON_HIDE_SYMBOLS)
#define CJSON_PUBLIC(type) type __stdcall
#elif defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllexport) type __stdcall
#elif defined(CJSON_IMPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllimport) type __stdcall
#endif
#else /* !WIN32 */
#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
#else
#define CJSON_PUBLIC(type) type
#endif
#endif
/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
* This is to prevent stack overflows. */
#ifndef CJSON_NESTING_LIMIT
#define CJSON_NESTING_LIMIT 1000
#endif
/* returns the version of cJSON as a string */
CJSON_PUBLIC(const char*) cJSON_Version(void);
/* Supply malloc, realloc and free functions to cJSON */
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
/* Render a cJSON entity to text for transfer/storage. */
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
/* Render a cJSON entity to text for transfer/storage without any formatting. */
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
/* Delete a cJSON entity and all subentities. */
CJSON_PUBLIC(void) cJSON_Delete(cJSON *c);
/* Returns the number of items in an array (or object). */
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
/* Get item "string" from object. Case insensitive. */
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
/* Check if the item is a string and return its valuestring */
CJSON_PUBLIC(char *) cJSON_GetStringValue(cJSON *item);
/* These functions check the type of an item */
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
/* These calls create a cJSON item of the appropriate type. */
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
/* raw json */
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
/* Create a string where valuestring references a string so
* it will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
/* Create an object/arrray that only references it's elements so
* they will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
/* These utilities create an Array of count items. */
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count);
/* Append item to the specified array/object. */
CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
* WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
* writing to `item->string` */
CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
/* Remove/Detatch items from Arrays/Objects. */
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
/* Update array items. */
CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
/* Duplicate a cJSON item */
CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
need to be released. With recurse!=0, it will duplicate any children connected to the item.
The item->next and ->prev pointers are always zero on return from Duplicate. */
/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
* case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);
CJSON_PUBLIC(void) cJSON_Minify(char *json);
/* Helper functions for creating and adding items to an object at the same time.
* They return the added item or NULL on failure. */
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
/* helper for the cJSON_SetNumberValue macro */
CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
/* Macro for iterating over an array or object */
#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
CJSON_PUBLIC(void) cJSON_free(void *object);
#endif
/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/* The crc32 functions and data was originally written by Spencer
* Garrett <srg@quick.com> and was gleaned from the PostgreSQL source
* tree via the files contrib/ltree/crc32.[ch] and from FreeBSD at
* src/usr.bin/cksum/crc32.c.
*/
#include "butil/third_party/libvbucket/hash.h"
static const uint32_t crc32tab[256] = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
};
uint32_t hash_crc32(const char *key, size_t key_length)
{
uint64_t x;
uint32_t crc= UINT32_MAX;
for (x= 0; x < key_length; x++)
crc= (crc >> 8) ^ crc32tab[(crc ^ (uint64_t)key[x]) & 0xff];
return ((~crc) >> 16) & 0x7fff;
}
/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* Copyright 2010 NorthScale, Inc.
*
* Licensed 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.
*/
#ifndef LIBVBUCKET_HASH_H
#define LIBVBUCKET_HASH_H 1
#include <stdint.h>
#include <sys/types.h>
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
namespace butil {
#endif
uint32_t hash_crc32(const char *key, size_t key_length);
uint32_t hash_ketama(const char *key, size_t key_length);
void hash_md5(const char *key, size_t key_length, unsigned char *result);
void* hash_md5_update(void *ctx, const char *key, size_t key_length);
void hash_md5_final(void *ctx, unsigned char *result);
#ifdef __cplusplus
} // namespace butil
}
#endif
#endif
/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
#include <stdlib.h>
#include "butil/third_party/libvbucket/hash.h"
/* This library uses the reference MD5 implementation from [RFC1321] */
#define PROTOTYPES 1
#include "butil/third_party/libvbucket/rfc1321/md5c.c"
#undef PROTOTYPES
void hash_md5(const char *key, size_t key_length, unsigned char *result)
{
MD5_CTX ctx;
MD5Init(&ctx);
MD5Update(&ctx, (unsigned char *)key, key_length);
MD5Final(result, &ctx);
}
void* hash_md5_update(void *ctx, const char *key, size_t key_length)
{
if (ctx == NULL) {
ctx = calloc(1, sizeof(MD5_CTX));
MD5Init(ctx);
}
MD5Update(ctx, (unsigned char *)key, key_length);
return ctx;
}
void hash_md5_final(void *ctx, unsigned char *result)
{
if (ctx == NULL) {
return;
}
MD5Final(result, ctx);
free(ctx);
}
uint32_t hash_ketama(const char *key, size_t key_length)
{
unsigned char digest[16];
hash_md5(key, key_length, digest);
return (uint32_t) ( (digest[3] << 24)
|(digest[2] << 16)
|(digest[1] << 8)
| digest[0]);
}
/* GLOBAL.H - RSAREF types and constants
*/
/* PROTOTYPES should be set to one if and only if the compiler supports
function argument prototyping.
The following makes PROTOTYPES default to 0 if it has not already
been defined with C compiler flags.
*/
#ifndef PROTOTYPES
#define PROTOTYPES 0
#endif
#include <stdint.h>
/* POINTER defines a generic pointer type */
typedef unsigned char *POINTER;
/* UINT2 defines a two byte word */
typedef uint16_t UINT2;
/* UINT4 defines a four byte word */
typedef uint32_t UINT4;
/* PROTO_LIST is defined depending on how PROTOTYPES is defined above.
If using PROTOTYPES, then PROTO_LIST returns the list, otherwise it
returns an empty list.
*/
#if PROTOTYPES
#define PROTO_LIST(list) list
#else
#define PROTO_LIST(list) ()
#endif
/* MD5.H - header file for MD5C.C
*/
/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
rights reserved.
License to copy and use this software is granted provided that it
is identified as the "RSA Data Security, Inc. MD5 Message-Digest
Algorithm" in all material mentioning or referencing this software
or this function.
License is also granted to make and use derivative works provided
that such works are identified as "derived from the RSA Data
Security, Inc. MD5 Message-Digest Algorithm" in all material
mentioning or referencing the derived work.
RSA Data Security, Inc. makes no representations concerning either
the merchantability of this software or the suitability of this
software for any particular purpose. It is provided "as is"
without express or implied warranty of any kind.
These notices must be retained in any copies of any part of this
documentation and/or software.
*/
/* MD5 context. */
typedef struct {
UINT4 state[4]; /* state (ABCD) */
UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */
unsigned char buffer[64]; /* input buffer */
} MD5_CTX;
void MD5Init PROTO_LIST ((MD5_CTX *));
void MD5Update PROTO_LIST ((MD5_CTX *, unsigned char *, unsigned int));
void MD5Final PROTO_LIST ((unsigned char [16], MD5_CTX *));
/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
*/
/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
rights reserved.
License to copy and use this software is granted provided that it
is identified as the "RSA Data Security, Inc. MD5 Message-Digest
Algorithm" in all material mentioning or referencing this software
or this function.
License is also granted to make and use derivative works provided
that such works are identified as "derived from the RSA Data
Security, Inc. MD5 Message-Digest Algorithm" in all material
mentioning or referencing the derived work.
RSA Data Security, Inc. makes no representations concerning either
the merchantability of this software or the suitability of this
software for any particular purpose. It is provided "as is"
without express or implied warranty of any kind.
These notices must be retained in any copies of any part of this
documentation and/or software.
*/
#include "butil/third_party/libvbucket/rfc1321/global.h"
#include "butil/third_party/libvbucket/rfc1321/md5.h"
/* Constants for MD5Transform routine.
*/
#define S11 7
#define S12 12
#define S13 17
#define S14 22
#define S21 5
#define S22 9
#define S23 14
#define S24 20
#define S31 4
#define S32 11
#define S33 16
#define S34 23
#define S41 6
#define S42 10
#define S43 15
#define S44 21
static void MD5Transform PROTO_LIST ((UINT4 [4], unsigned char [64]));
static void Encode PROTO_LIST
((unsigned char *, UINT4 *, unsigned int));
static void Decode PROTO_LIST
((UINT4 *, unsigned char *, unsigned int));
static void MD5_memcpy PROTO_LIST ((POINTER, POINTER, unsigned int));
static void MD5_memset PROTO_LIST ((POINTER, int, unsigned int));
static unsigned char PADDING[64] = {
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
/* F, G, H and I are basic MD5 functions.
*/
#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define I(x, y, z) ((y) ^ ((x) | (~z)))
/* ROTATE_LEFT rotates x left n bits.
*/
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
Rotation is separate from addition to prevent recomputation.
*/
#define FF(a, b, c, d, x, s, ac) { \
(a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
#define GG(a, b, c, d, x, s, ac) { \
(a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
#define HH(a, b, c, d, x, s, ac) { \
(a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
#define II(a, b, c, d, x, s, ac) { \
(a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
/* MD5 initialization. Begins an MD5 operation, writing a new context.
*/
void MD5Init (context)
MD5_CTX *context; /* context */
{
context->count[0] = context->count[1] = 0;
/* Load magic initialization constants.
*/
context->state[0] = 0x67452301;
context->state[1] = 0xefcdab89;
context->state[2] = 0x98badcfe;
context->state[3] = 0x10325476;
}
/* MD5 block update operation. Continues an MD5 message-digest
operation, processing another message block, and updating the
context.
*/
void MD5Update (context, input, inputLen)
MD5_CTX *context; /* context */
unsigned char *input; /* input block */
unsigned int inputLen; /* length of input block */
{
unsigned int i, index, partLen;
/* Compute number of bytes mod 64 */
index = (unsigned int)((context->count[0] >> 3) & 0x3F);
/* Update number of bits */
if ((context->count[0] += ((UINT4)inputLen << 3))
< ((UINT4)inputLen << 3))
context->count[1]++;
context->count[1] += ((UINT4)inputLen >> 29);
partLen = 64 - index;
/* Transform as many times as possible.
*/
if (inputLen >= partLen) {
MD5_memcpy
((POINTER)&context->buffer[index], (POINTER)input, partLen);
MD5Transform (context->state, context->buffer);
for (i = partLen; i + 63 < inputLen; i += 64)
MD5Transform (context->state, &input[i]);
index = 0;
}
else
i = 0;
/* Buffer remaining input */
MD5_memcpy
((POINTER)&context->buffer[index], (POINTER)&input[i],
inputLen-i);
}
/* MD5 finalization. Ends an MD5 message-digest operation, writing the
the message digest and zeroizing the context.
*/
void MD5Final (digest, context)
unsigned char digest[16]; /* message digest */
MD5_CTX *context; /* context */
{
unsigned char bits[8];
unsigned int index, padLen;
/* Save number of bits */
Encode (bits, context->count, 8);
/* Pad out to 56 mod 64.
*/
index = (unsigned int)((context->count[0] >> 3) & 0x3f);
padLen = (index < 56) ? (56 - index) : (120 - index);
MD5Update (context, PADDING, padLen);
/* Append length (before padding) */
MD5Update (context, bits, 8);
/* Store state in digest */
Encode (digest, context->state, 16);
/* Zeroize sensitive information.
*/
MD5_memset ((POINTER)context, 0, sizeof (*context));
}
/* MD5 basic transformation. Transforms state based on block.
*/
static void MD5Transform (state, block)
UINT4 state[4];
unsigned char block[64];
{
UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
Decode (x, block, 64);
/* Round 1 */
FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
/* Round 2 */
GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */
GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
/* Round 3 */
HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */
HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */
/* Round 4 */
II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
/* Zeroize sensitive information.
*/
MD5_memset ((POINTER)x, 0, sizeof (x));
}
/* Encodes input (UINT4) into output (unsigned char). Assumes len is
a multiple of 4.
*/
static void Encode (output, input, len)
unsigned char *output;
UINT4 *input;
unsigned int len;
{
unsigned int i, j;
for (i = 0, j = 0; j < len; i++, j += 4) {
output[j] = (unsigned char)(input[i] & 0xff);
output[j+1] = (unsigned char)((input[i] >> 8) & 0xff);
output[j+2] = (unsigned char)((input[i] >> 16) & 0xff);
output[j+3] = (unsigned char)((input[i] >> 24) & 0xff);
}
}
/* Decodes input (unsigned char) into output (UINT4). Assumes len is
a multiple of 4.
*/
static void Decode (output, input, len)
UINT4 *output;
unsigned char *input;
unsigned int len;
{
unsigned int i, j;
for (i = 0, j = 0; j < len; i++, j += 4)
output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) |
(((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24);
}
/* Note: Replace "for loop" with standard memcpy if possible.
*/
static void MD5_memcpy (output, input, len)
POINTER output;
POINTER input;
unsigned int len;
{
unsigned int i;
for (i = 0; i < len; i++)
output[i] = input[i];
}
/* Note: Replace "for loop" with standard memset if possible.
*/
static void MD5_memset (output, value, len)
POINTER output;
int value;
unsigned int len;
{
unsigned int i;
for (i = 0; i < len; i++)
((char *)output)[i] = (char)value;
}
/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* Copyright 2010 NorthScale, Inc.
*
* Licensed 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 <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include "butil/third_party/libvbucket/cJSON.h"
#include "butil/third_party/libvbucket/hash.h"
#include "butil/third_party/libvbucket/vbucket.h"
#define MAX_CONFIG_SIZE 100 * 1048576
#define MAX_VBUCKETS 65536
#define MAX_REPLICAS 4
#define MAX_AUTHORITY_SIZE 100
#define STRINGIFY_(X) #X
#define STRINGIFY(X) STRINGIFY_(X)
struct server_st {
char *authority; /* host:port */
char *rest_api_authority;
char *couchdb_api_base;
int config_node; /* non-zero if server struct describes node,
which is listening */
};
struct vbucket_st {
int servers[MAX_REPLICAS + 1];
};
struct continuum_item_st {
uint32_t index; /* server index */
uint32_t point; /* point on the ketama continuum */
};
struct vbucket_config_st {
char *errmsg;
VBUCKET_DISTRIBUTION_TYPE distribution;
int num_vbuckets;
int mask;
int num_servers;
int num_replicas;
char *user;
char *password;
int num_continuum; /* count of continuum points */
struct continuum_item_st *continuum; /* ketama continuum */
struct server_st *servers;
struct vbucket_st *fvbuckets;
struct vbucket_st *vbuckets;
const char *localhost; /* replacement for $HOST placeholder */
size_t nlocalhost;
};
static char *errstr = NULL;
const char *vbucket_get_error() {
return errstr;
}
static int continuum_item_cmp(const void *t1, const void *t2)
{
const struct continuum_item_st *ct1 = t1, *ct2 = t2;
if (ct1->point == ct2->point) {
return 0;
} else if (ct1->point > ct2->point) {
return 1;
} else {
return -1;
}
}
static void update_ketama_continuum(VBUCKET_CONFIG_HANDLE vb)
{
char host[MAX_AUTHORITY_SIZE+10] = "";
int nhost;
int pp, hh, ss, nn;
unsigned char digest[16];
struct continuum_item_st *new_continuum, *old_continuum;
new_continuum = calloc(160 * vb->num_servers,
sizeof(struct continuum_item_st));
/* 40 hashes, 4 numbers per hash = 160 points per server */
for (ss = 0, pp = 0; ss < vb->num_servers; ++ss) {
/* we can add more points to server which have more memory */
for (hh = 0; hh < 40; ++hh) {
nhost = snprintf(host, MAX_AUTHORITY_SIZE+10, "%s-%u",
vb->servers[ss].authority, hh);
hash_md5(host, nhost, digest);
for (nn = 0; nn < 4; ++nn, ++pp) {
new_continuum[pp].index = ss;
new_continuum[pp].point = ((uint32_t) (digest[3 + nn * 4] & 0xFF) << 24)
| ((uint32_t) (digest[2 + nn * 4] & 0xFF) << 16)
| ((uint32_t) (digest[1 + nn * 4] & 0xFF) << 8)
| (digest[0 + nn * 4] & 0xFF);
}
}
}
qsort(new_continuum, pp, sizeof(struct continuum_item_st), continuum_item_cmp);
old_continuum = vb->continuum;
vb->continuum = new_continuum;
vb->num_continuum = pp;
if (old_continuum) {
free(old_continuum);
}
}
void vbucket_config_destroy(VBUCKET_CONFIG_HANDLE vb) {
int i;
for (i = 0; i < vb->num_servers; ++i) {
free(vb->servers[i].authority);
free(vb->servers[i].rest_api_authority);
free(vb->servers[i].couchdb_api_base);
}
free(vb->servers);
free(vb->user);
free(vb->password);
free(vb->fvbuckets);
free(vb->vbuckets);
free(vb->continuum);
free(vb->errmsg);
memset(vb, 0xff, sizeof(struct vbucket_config_st));
free(vb);
}
static char *substitute_localhost_marker(struct vbucket_config_st *vb, char *input)
{
char *placeholder;
char *result = input;
size_t ninput = strlen(input);
if (vb->localhost && (placeholder = strstr(input, "$HOST"))) {
size_t nprefix = placeholder - input;
size_t off = 0;
result = calloc(ninput + vb->nlocalhost - 5, sizeof(char));
if (!result) {
return NULL;
}
memcpy(result, input, nprefix);
off += nprefix;
memcpy(result + off, vb->localhost, vb->nlocalhost);
off += vb->nlocalhost;
memcpy(result + off, input + nprefix + 5, ninput - (nprefix + 5));
free(input);
}
return result;
}
static int populate_servers(struct vbucket_config_st *vb, cJSON *c) {
int i;
vb->servers = calloc(vb->num_servers, sizeof(struct server_st));
if (vb->servers == NULL) {
vbucket_config_destroy(vb);
vb->errmsg = strdup("Failed to allocate servers array");
return -1;
}
for (i = 0; i < vb->num_servers; ++i) {
char *server;
cJSON *jServer = cJSON_GetArrayItem(c, i);
if (jServer == NULL || jServer->type != cJSON_String) {
vb->errmsg = strdup("Expected array of strings for serverList");
return -1;
}
server = strdup(jServer->valuestring);
if (server == NULL) {
vb->errmsg = strdup("Failed to allocate storage for server string");
return -1;
}
server = substitute_localhost_marker(vb, server);
if (server == NULL) {
vb->errmsg = strdup("Failed to allocate storage for server string during $HOST substitution");
return -1;
}
vb->servers[i].authority = server;
}
return 0;
}
static int get_node_authority(struct vbucket_config_st *vb, cJSON *node, char **out, size_t nbuf)
{
cJSON *json;
char *hostname = NULL, *colon = NULL;
int port = -1;
char *buf = *out;
json = cJSON_GetObjectItem(node, "hostname");
if (json == NULL || json->type != cJSON_String) {
vb->errmsg = strdup("Expected string for node's hostname");
return -1;
}
hostname = json->valuestring;
json = cJSON_GetObjectItem(node, "ports");
if (json == NULL || json->type != cJSON_Object) {
vb->errmsg = strdup("Expected json object for node's ports");
return -1;
}
json = cJSON_GetObjectItem(json, "direct");
if (json == NULL || json->type != cJSON_Number) {
vb->errmsg = strdup("Expected number for node's direct port");
return -1;
}
port = json->valueint;
snprintf(buf, nbuf - 7, "%s", hostname);
colon = strchr(buf, ':');
if (!colon) {
colon = buf + strlen(buf);
}
snprintf(colon, 7, ":%d", port);
buf = substitute_localhost_marker(vb, buf);
if (buf == NULL) {
vb->errmsg = strdup("Failed to allocate storage for authority string during $HOST substitution");
return -1;
}
*out = buf;
return 0;
}
static int lookup_server_struct(struct vbucket_config_st *vb, cJSON *c) {
char *authority = NULL;
int idx = -1, ii;
authority = calloc(MAX_AUTHORITY_SIZE, sizeof(char));
if (authority == NULL) {
vb->errmsg = strdup("Failed to allocate storage for authority string");
return -1;
}
if (get_node_authority(vb, c, &authority, MAX_AUTHORITY_SIZE) < 0) {
free(authority);
return -1;
}
for (ii = 0; ii < vb->num_servers; ++ii) {
if (strcmp(vb->servers[ii].authority, authority) == 0) {
idx = ii;
break;
}
}
free(authority);
return idx;
}
static int update_server_info(struct vbucket_config_st *vb, cJSON *config) {
int idx, ii;
cJSON *node, *json;
for (ii = 0; ii < cJSON_GetArraySize(config); ++ii) {
node = cJSON_GetArrayItem(config, ii);
if (node) {
if (node->type != cJSON_Object) {
vb->errmsg = strdup("Expected json object for nodes array item");
return -1;
}
if ((idx = lookup_server_struct(vb, node)) >= 0) {
json = cJSON_GetObjectItem(node, "couchApiBase");
if (json != NULL) {
char *value = strdup(json->valuestring);
if (value == NULL) {
vb->errmsg = strdup("Failed to allocate storage for couchApiBase string");
return -1;
}
value = substitute_localhost_marker(vb, value);
if (value == NULL) {
vb->errmsg = strdup("Failed to allocate storage for hostname string during $HOST substitution");
return -1;
}
vb->servers[idx].couchdb_api_base = value;
}
json = cJSON_GetObjectItem(node, "hostname");
if (json != NULL) {
char *value = strdup(json->valuestring);
if (value == NULL) {
vb->errmsg = strdup("Failed to allocate storage for hostname string");
return -1;
}
value = substitute_localhost_marker(vb, value);
if (value == NULL) {
vb->errmsg = strdup("Failed to allocate storage for hostname string during $HOST substitution");
return -1;
}
vb->servers[idx].rest_api_authority = value;
}
json = cJSON_GetObjectItem(node, "thisNode");
if (json != NULL && json->type == cJSON_True) {
vb->servers[idx].config_node = 1;
}
}
}
}
return 0;
}
static int populate_buckets(struct vbucket_config_st *vb, cJSON *c, int is_forward)
{
int i, j;
struct vbucket_st *vb_map = NULL;
if (is_forward) {
if (!(vb->fvbuckets = vb_map = calloc(vb->num_vbuckets, sizeof(struct vbucket_st)))) {
vb->errmsg = strdup("Failed to allocate storage for forward vbucket map");
return -1;
}
} else {
if (!(vb->vbuckets = vb_map = calloc(vb->num_vbuckets, sizeof(struct vbucket_st)))) {
vb->errmsg = strdup("Failed to allocate storage for vbucket map");
return -1;
}
}
for (i = 0; i < vb->num_vbuckets; ++i) {
cJSON *jBucket = cJSON_GetArrayItem(c, i);
if (jBucket == NULL || jBucket->type != cJSON_Array ||
cJSON_GetArraySize(jBucket) != vb->num_replicas + 1) {
vb->errmsg = strdup("Expected array of arrays each with numReplicas + 1 ints for vBucketMap");
return -1;
}
for (j = 0; j < vb->num_replicas + 1; ++j) {
cJSON *jServerId = cJSON_GetArrayItem(jBucket, j);
if (jServerId == NULL || jServerId->type != cJSON_Number ||
jServerId->valueint < -1 || jServerId->valueint >= vb->num_servers) {
vb->errmsg = strdup("Server ID must be >= -1 and < num_servers");
return -1;
}
vb_map[i].servers[j] = jServerId->valueint;
}
}
return 0;
}
static int parse_vbucket_config(VBUCKET_CONFIG_HANDLE vb, cJSON *c)
{
cJSON *json, *config;
config = cJSON_GetObjectItem(c, "vBucketServerMap");
if (config == NULL || config->type != cJSON_Object) {
/* seems like config without envelop, try to parse it */
config = c;
}
json = cJSON_GetObjectItem(config, "numReplicas");
if (json == NULL || json->type != cJSON_Number ||
json->valueint > MAX_REPLICAS) {
vb->errmsg = strdup("Expected number <= " STRINGIFY(MAX_REPLICAS) " for numReplicas");
return -1;
}
vb->num_replicas = json->valueint;
json = cJSON_GetObjectItem(config, "serverList");
if (json == NULL || json->type != cJSON_Array) {
vb->errmsg = strdup("Expected array for serverList");
return -1;
}
vb->num_servers = cJSON_GetArraySize(json);
if (vb->num_servers == 0) {
vb->errmsg = strdup("Empty serverList");
return -1;
}
if (populate_servers(vb, json) != 0) {
return -1;
}
/* optionally update server info using envelop (couchdb_api_base etc.) */
json = cJSON_GetObjectItem(c, "nodes");
if (json) {
if (json->type != cJSON_Array) {
vb->errmsg = strdup("Expected array for nodes");
return -1;
}
if (update_server_info(vb, json) != 0) {
return -1;
}
}
json = cJSON_GetObjectItem(config, "vBucketMap");
if (json == NULL || json->type != cJSON_Array) {
vb->errmsg = strdup("Expected array for vBucketMap");
return -1;
}
vb->num_vbuckets = cJSON_GetArraySize(json);
if (vb->num_vbuckets == 0 || (vb->num_vbuckets & (vb->num_vbuckets - 1)) != 0) {
vb->errmsg = strdup("Number of vBuckets must be a power of two > 0 and <= " STRINGIFY(MAX_VBUCKETS));
return -1;
}
vb->mask = vb->num_vbuckets - 1;
if (populate_buckets(vb, json, 0) != 0) {
return -1;
}
/* vbucket forward map could possibly be null */
json = cJSON_GetObjectItem(config, "vBucketMapForward");
if (json) {
if (json->type != cJSON_Array) {
vb->errmsg = strdup("Expected array for vBucketMapForward");
return -1;
}
if (populate_buckets(vb, json, 1) !=0) {
return -1;
}
}
return 0;
}
static int server_cmp(const void *s1, const void *s2)
{
return strcmp(((const struct server_st *)s1)->authority,
((const struct server_st *)s2)->authority);
}
static int parse_ketama_config(VBUCKET_CONFIG_HANDLE vb, cJSON *config)
{
cJSON *json, *node, *hostname;
char *buf;
int ii;
json = cJSON_GetObjectItem(config, "nodes");
if (json == NULL || json->type != cJSON_Array) {
vb->errmsg = strdup("Expected array for nodes");
return -1;
}
vb->num_servers = cJSON_GetArraySize(json);
if (vb->num_servers == 0) {
vb->errmsg = strdup("Empty serverList");
return -1;
}
vb->servers = calloc(vb->num_servers, sizeof(struct server_st));
for (ii = 0; ii < vb->num_servers; ++ii) {
node = cJSON_GetArrayItem(json, ii);
if (node == NULL || node->type != cJSON_Object) {
vb->errmsg = strdup("Expected object for nodes array item");
return -1;
}
buf = calloc(MAX_AUTHORITY_SIZE, sizeof(char));
if (buf == NULL) {
vb->errmsg = strdup("Failed to allocate storage for node authority");
return -1;
}
if (get_node_authority(vb, node, &buf, MAX_AUTHORITY_SIZE) < 0) {
return -1;
}
vb->servers[ii].authority = buf;
hostname = cJSON_GetObjectItem(node, "hostname");
if (hostname == NULL || hostname->type != cJSON_String) {
vb->errmsg = strdup("Expected string for node's hostname");
return -1;
}
buf = strdup(hostname->valuestring);
if (buf == NULL) {
vb->errmsg = strdup("Failed to allocate storage for hostname string");
return -1;
}
buf = substitute_localhost_marker(vb, buf);
if (buf == NULL) {
vb->errmsg = strdup("Failed to allocate storage for hostname string during $HOST substitution");
return -1;
}
vb->servers[ii].rest_api_authority = buf;
}
qsort(vb->servers, vb->num_servers, sizeof(struct server_st), server_cmp);
update_ketama_continuum(vb);
return 0;
}
static int parse_cjson(VBUCKET_CONFIG_HANDLE handle, cJSON *config)
{
cJSON *json;
/* set optional credentials */
json = cJSON_GetObjectItem(config, "name");
if (json != NULL && json->type == cJSON_String && strcmp(json->valuestring, "default") != 0) {
handle->user = strdup(json->valuestring);
}
json = cJSON_GetObjectItem(config, "saslPassword");
if (json != NULL && json->type == cJSON_String) {
handle->password = strdup(json->valuestring);
}
/* by default it uses vbucket distribution to map keys to servers */
handle->distribution = VBUCKET_DISTRIBUTION_VBUCKET;
json = cJSON_GetObjectItem(config, "nodeLocator");
if (json == NULL) {
/* special case: it migth be config without envelope */
if (parse_vbucket_config(handle, config) == -1) {
return -1;
}
} else if (json->type == cJSON_String) {
if (strcmp(json->valuestring, "vbucket") == 0) {
handle->distribution = VBUCKET_DISTRIBUTION_VBUCKET;
if (parse_vbucket_config(handle, config) == -1) {
return -1;
}
} else if (strcmp(json->valuestring, "ketama") == 0) {
handle->distribution = VBUCKET_DISTRIBUTION_KETAMA;
if (parse_ketama_config(handle, config) == -1) {
return -1;
}
}
} else {
handle->errmsg = strdup("Expected string for nodeLocator");
return -1;
}
return 0;
}
static int parse_from_memory(VBUCKET_CONFIG_HANDLE handle, const char *data)
{
int ret;
cJSON *c = cJSON_Parse(data);
if (c == NULL) {
handle->errmsg = strdup("Failed to parse data. Invalid JSON?");
return -1;
}
ret = parse_cjson(handle, c);
cJSON_Delete(c);
return ret;
}
static int do_read_file(FILE *fp, char *data, size_t size)
{
size_t offset = 0;
size_t nread;
do {
nread = fread(data + offset, 1, size, fp);
if (nread != (size_t)-1 && nread != 0) {
offset += nread;
size -= nread;
} else {
return -1;
}
} while (size > 0);
return 0;
}
static int parse_from_file(VBUCKET_CONFIG_HANDLE handle, const char *filename)
{
long size;
char *data;
int ret;
FILE *f = fopen(filename, "rb");
if (f == NULL) {
char msg[1024];
snprintf(msg, sizeof(msg), "Unable to open file \"%s\": %s", filename,
strerror(errno));
handle->errmsg = strdup(msg);
return -1;
}
fseek(f, 0, SEEK_END);
size = ftell(f);
fseek(f, 0, SEEK_SET);
if (size > MAX_CONFIG_SIZE) {
char msg[1024];
snprintf(msg, sizeof(msg), "File too large: \"%s\"", filename);
handle->errmsg = strdup(msg);
fclose(f);
return -1;
}
data = calloc(size+1, sizeof(char));
if (data == NULL) {
char msg[1024];
snprintf(msg, sizeof(msg), "Failed to allocate buffer to read: \"%s\"", filename);
handle->errmsg = strdup(msg);
fclose(f);
return -1;
}
if (do_read_file(f, data, size) == -1) {
char msg[1024];
snprintf(msg, sizeof(msg), "Failed to read entire file: \"%s\": %s",
filename, strerror(errno));
handle->errmsg = strdup(msg);
fclose(f);
free(data);
return -1;
}
fclose(f);
ret = parse_from_memory(handle, data);
free(data);
return ret;
}
VBUCKET_CONFIG_HANDLE vbucket_config_create(void)
{
return calloc(1, sizeof(struct vbucket_config_st));
}
int vbucket_config_parse2(VBUCKET_CONFIG_HANDLE handle,
vbucket_source_t data_source,
const char *data,
const char *peername)
{
handle->localhost = peername;
handle->nlocalhost = peername ? strlen(peername) : 0;
if (data_source == LIBVBUCKET_SOURCE_FILE) {
return parse_from_file(handle, data);
} else {
return parse_from_memory(handle, data);
}
}
int vbucket_config_parse(VBUCKET_CONFIG_HANDLE handle,
vbucket_source_t data_source,
const char *data)
{
return vbucket_config_parse2(handle, data_source, data, "localhost");
}
const char *vbucket_get_error_message(VBUCKET_CONFIG_HANDLE handle)
{
return handle->errmsg;
}
static VBUCKET_CONFIG_HANDLE backwards_compat(vbucket_source_t source, const char *data)
{
VBUCKET_CONFIG_HANDLE ret = vbucket_config_create();
if (ret == NULL) {
return NULL;
}
if (vbucket_config_parse(ret, source, data) != 0) {
errstr = strdup(ret->errmsg);
vbucket_config_destroy(ret);
ret = NULL;
}
return ret;
}
VBUCKET_CONFIG_HANDLE vbucket_config_parse_file(const char *filename)
{
return backwards_compat(LIBVBUCKET_SOURCE_FILE, filename);
}
VBUCKET_CONFIG_HANDLE vbucket_config_parse_string(const char *data)
{
return backwards_compat(LIBVBUCKET_SOURCE_MEMORY, data);
}
int vbucket_map(VBUCKET_CONFIG_HANDLE vb, const void *key, size_t nkey,
int *vbucket_id, int *server_idx)
{
uint32_t digest, mid, prev;
struct continuum_item_st *beginp, *endp, *midp, *highp, *lowp;
if (vb->distribution == VBUCKET_DISTRIBUTION_KETAMA) {
assert(vb->continuum);
if (vbucket_id) {
*vbucket_id = 0;
}
digest = hash_ketama(key, nkey);
beginp = lowp = vb->continuum;
endp = highp = vb->continuum + vb->num_continuum;
/* divide and conquer array search to find server with next biggest
* point after what this key hashes to */
while (1)
{
/* pick the middle point */
midp = lowp + (highp - lowp) / 2;
if (midp == endp) {
/* if at the end, roll back to zeroth */
*server_idx = beginp->index;
break;
}
mid = midp->point;
prev = (midp == beginp) ? 0 : (midp-1)->point;
if (digest <= mid && digest > prev) {
/* we found nearest server */
*server_idx = midp->index;
break;
}
/* adjust the limits */
if (mid < digest) {
lowp = midp + 1;
} else {
highp = midp - 1;
}
if (lowp > highp) {
*server_idx = beginp->index;
break;
}
}
} else {
*vbucket_id = vbucket_get_vbucket_by_key(vb, key, nkey);
*server_idx = vbucket_get_master(vb, *vbucket_id);
}
return 0;
}
int vbucket_config_get_num_replicas(VBUCKET_CONFIG_HANDLE vb) {
return vb->num_replicas;
}
int vbucket_config_get_num_vbuckets(VBUCKET_CONFIG_HANDLE vb) {
return vb->num_vbuckets;
}
int vbucket_config_get_num_servers(VBUCKET_CONFIG_HANDLE vb) {
return vb->num_servers;
}
const char *vbucket_config_get_couch_api_base(VBUCKET_CONFIG_HANDLE vb, int i) {
return vb->servers[i].couchdb_api_base;
}
const char *vbucket_config_get_rest_api_server(VBUCKET_CONFIG_HANDLE vb, int i) {
return vb->servers[i].rest_api_authority;
}
int vbucket_config_is_config_node(VBUCKET_CONFIG_HANDLE vb, int i) {
return vb->servers[i].config_node;
}
VBUCKET_DISTRIBUTION_TYPE vbucket_config_get_distribution_type(VBUCKET_CONFIG_HANDLE vb) {
return vb->distribution;
}
const char *vbucket_config_get_server(VBUCKET_CONFIG_HANDLE vb, int i) {
return vb->servers[i].authority;
}
const char *vbucket_config_get_user(VBUCKET_CONFIG_HANDLE vb) {
return vb->user;
}
const char *vbucket_config_get_password(VBUCKET_CONFIG_HANDLE vb) {
return vb->password;
}
int vbucket_get_vbucket_by_key(VBUCKET_CONFIG_HANDLE vb, const void *key, size_t nkey) {
/* call crc32 directly here it could be changed to some more general
* function when vbucket distribution will support multiple hashing
* algorithms */
uint32_t digest = hash_crc32(key, nkey);
return digest & vb->mask;
}
int vbucket_get_master(VBUCKET_CONFIG_HANDLE vb, int vbucket) {
return vb->vbuckets[vbucket].servers[0];
}
int vbucket_get_replica(VBUCKET_CONFIG_HANDLE vb, int vbucket, int i) {
int idx = i + 1;
if (idx < vb->num_servers) {
return vb->vbuckets[vbucket].servers[idx];
} else {
return -1;
}
}
int vbucket_found_incorrect_master(VBUCKET_CONFIG_HANDLE vb, int vbucket,
int wrongserver) {
int mappedServer = vb->vbuckets[vbucket].servers[0];
int rv = mappedServer;
/*
* if a forward table exists, then return the vbucket id from the forward table
* and update that information in the current table. We also need to Update the
* replica information for that vbucket
*/
if (vb->fvbuckets) {
int i = 0;
rv = vb->vbuckets[vbucket].servers[0] = vb->fvbuckets[vbucket].servers[0];
for (i = 0; i < vb->num_replicas; i++) {
vb->vbuckets[vbucket].servers[i+1] = vb->fvbuckets[vbucket].servers[i+1];
}
} else if (mappedServer == wrongserver) {
rv = (rv + 1) % vb->num_servers;
vb->vbuckets[vbucket].servers[0] = rv;
}
return rv;
}
static void compute_vb_list_diff(VBUCKET_CONFIG_HANDLE from,
VBUCKET_CONFIG_HANDLE to,
char **out) {
int offset = 0;
int i, j;
for (i = 0; i < to->num_servers; i++) {
int found = 0;
const char *sn = vbucket_config_get_server(to, i);
for (j = 0; !found && j < from->num_servers; j++) {
const char *sn2 = vbucket_config_get_server(from, j);
found |= (strcmp(sn2, sn) == 0);
}
if (!found) {
out[offset] = strdup(sn);
assert(out[offset]);
++offset;
}
}
}
VBUCKET_CONFIG_DIFF* vbucket_compare(VBUCKET_CONFIG_HANDLE from,
VBUCKET_CONFIG_HANDLE to) {
VBUCKET_CONFIG_DIFF *rv = calloc(1, sizeof(VBUCKET_CONFIG_DIFF));
int num_servers = (from->num_servers > to->num_servers
? from->num_servers : to->num_servers) + 1;
assert(rv);
rv->servers_added = calloc(num_servers, sizeof(char*));
rv->servers_removed = calloc(num_servers, sizeof(char*));
/* Compute the added and removed servers */
compute_vb_list_diff(from, to, rv->servers_added);
compute_vb_list_diff(to, from, rv->servers_removed);
/* Verify the servers are equal in their positions */
if (to->num_servers == from->num_servers) {
int i;
for (i = 0; i < from->num_servers; i++) {
rv->sequence_changed |= (0 != strcmp(vbucket_config_get_server(from, i),
vbucket_config_get_server(to, i)));
}
} else {
/* Just say yes */
rv->sequence_changed = 1;
}
/* Consider the sequence changed if the auth credentials changed */
if (from->user != NULL && to->user != NULL) {
rv->sequence_changed |= (strcmp(from->user, to->user) != 0);
} else {
rv->sequence_changed |= ((from->user != NULL) ^ (to->user != NULL));
}
if (from->password != NULL && to->password != NULL) {
rv->sequence_changed |= (strcmp(from->password, to->password) != 0);
} else {
rv->sequence_changed |= ((from->password != NULL) ^ (to->password != NULL));
}
/* Count the number of vbucket differences */
if (to->num_vbuckets == from->num_vbuckets) {
int i;
for (i = 0; i < to->num_vbuckets; i++) {
rv->n_vb_changes += (vbucket_get_master(from, i)
== vbucket_get_master(to, i)) ? 0 : 1;
}
} else {
rv->n_vb_changes = -1;
}
return rv;
}
static void free_array_helper(char **l) {
int i;
for (i = 0; l[i]; i++) {
free(l[i]);
}
free(l);
}
void vbucket_free_diff(VBUCKET_CONFIG_DIFF *diff) {
assert(diff);
free_array_helper(diff->servers_added);
free_array_helper(diff->servers_removed);
free(diff);
}
/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* Copyright 2010 NorthScale, Inc.
*
* Licensed 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.
*/
/*! \mainpage libvbucket
*
* \section intro_sec Introduction
*
* libvbucket helps you understand and make use of vbuckets for
* scaling kv services.
*
* \section docs_sec API Documentation
*
* Jump right into <a href="modules.html">the modules docs</a> to get started.
*/
/**
* VBucket Utility Library.
*
* \defgroup CD Creation and Destruction
* \defgroup cfg Config Access
* \defgroup cfgcmp Config Comparison
* \defgroup vb VBucket Access
* \defgroup err Error handling
*/
#ifndef LIBVBUCKET_VBUCKET_H
#define LIBVBUCKET_VBUCKET_H 1
#include <stddef.h>
#include "butil/third_party/libvbucket/visibility.h"
#ifdef __cplusplus
extern "C" {
namespace butil {
#endif
struct vbucket_config_st;
/**
* Opaque config representation.
*/
typedef struct vbucket_config_st* VBUCKET_CONFIG_HANDLE;
/**
* Type of distribution used to map keys to servers. It is possible to
* select algorithm using "locator" key in config.
*/
typedef enum {
VBUCKET_DISTRIBUTION_VBUCKET = 0,
VBUCKET_DISTRIBUTION_KETAMA = 1
} VBUCKET_DISTRIBUTION_TYPE;
/**
* \addtogroup cfgcmp
* @{
*/
/**
* Difference between two vbucket configs.
*/
typedef struct {
/**
* NULL-terminated list of server names that were added.
*/
char **servers_added;
/**
* NULL-terminated list of server names that were removed.
*/
char **servers_removed;
/**
* Number of vbuckets that changed. -1 if the total number changed
*/
int n_vb_changes;
/**
* non-null if the sequence of servers changed.
*/
int sequence_changed;
} VBUCKET_CONFIG_DIFF;
/**
* @}
*/
/**
* \addtogroup CD
* @{
*/
/**
* Create a new vbucket config handle
* @return handle or NULL if there is no more memory
*/
LIBVBUCKET_PUBLIC_API
VBUCKET_CONFIG_HANDLE vbucket_config_create(void);
typedef enum {
LIBVBUCKET_SOURCE_FILE,
LIBVBUCKET_SOURCE_MEMORY
} vbucket_source_t;
/**
* Parse a vbucket configuration
* @param handle the vbucket config handle to store the result
* @param data_source what kind of datasource to parse
* @param data A zero terminated string representing the data to parse.
* For LIBVBUCKET_SOURCE_FILE this is the file to parse,
* for LIBVBUCKET_SOURCE_MEMORY it is the actual JSON body.
* @return 0 for success, the appropriate error code otherwise
*/
LIBVBUCKET_PUBLIC_API
int vbucket_config_parse(VBUCKET_CONFIG_HANDLE handle,
vbucket_source_t data_source,
const char *data);
/**
* Parse a vbucket configuration and substitute local address if needed
* @param handle the vbucket config handle to store the result
* @param data_source what kind of datasource to parse
* @param data A zero terminated string representing the data to parse.
* For LIBVBUCKET_SOURCE_FILE this is the file to parse,
* for LIBVBUCKET_SOURCE_MEMORY it is the actual JSON body.
* @param peername a string, representing address of local peer
* (usually 127.0.0.1)
* @return 0 for success, the appropriate error code otherwise
*/
LIBVBUCKET_PUBLIC_API
int vbucket_config_parse2(VBUCKET_CONFIG_HANDLE handle,
vbucket_source_t data_source,
const char *data,
const char *peername);
LIBVBUCKET_PUBLIC_API
const char *vbucket_get_error_message(VBUCKET_CONFIG_HANDLE handle);
/**
* Create an instance of vbucket config from a file.
*
* @param filename the vbucket config to parse
*/
LIBVBUCKET_PUBLIC_API
VBUCKET_CONFIG_HANDLE vbucket_config_parse_file(const char *filename);
/**
* Create an instance of vbucket config from a string.
*
* @param data a vbucket config string.
*/
LIBVBUCKET_PUBLIC_API
VBUCKET_CONFIG_HANDLE vbucket_config_parse_string(const char *data);
/**
* Destroy a vbucket config.
*
* @param h the vbucket config handle
*/
LIBVBUCKET_PUBLIC_API
void vbucket_config_destroy(VBUCKET_CONFIG_HANDLE h);
/**
* @}
*/
/**
* \addtogroup err
* @{
*/
/**
* Get the most recent vbucket error.
*
* This is currently not threadsafe.
*/
LIBVBUCKET_PUBLIC_API
const char *vbucket_get_error(void);
/**
* Tell libvbucket it told you the wrong server ID.
*
* This will cause libvbucket to do whatever is necessary to try
* to figure out a better answer.
*
* @param h the vbucket config handle.
* @param vbucket the vbucket ID
* @param wrongserver the incorrect server ID
*
* @return the correct server ID
*/
LIBVBUCKET_PUBLIC_API
int vbucket_found_incorrect_master(VBUCKET_CONFIG_HANDLE h,
int vbucket,
int wrongserver);
/**
* @}
*/
/**
* \addtogroup cfg
* @{
*/
/**
* Get the number of replicas configured for each vbucket.
*/
LIBVBUCKET_PUBLIC_API
int vbucket_config_get_num_replicas(VBUCKET_CONFIG_HANDLE h);
/**
* Get the total number of vbuckets;
*/
LIBVBUCKET_PUBLIC_API
int vbucket_config_get_num_vbuckets(VBUCKET_CONFIG_HANDLE h);
/**
* Get the total number of known servers.
*/
LIBVBUCKET_PUBLIC_API
int vbucket_config_get_num_servers(VBUCKET_CONFIG_HANDLE h);
/**
* Get the optional SASL user.
*
* @return a string or NULL.
*/
LIBVBUCKET_PUBLIC_API
const char *vbucket_config_get_user(VBUCKET_CONFIG_HANDLE h);
/**
* Get the optional SASL password.
*
* @return a string or NULL.
*/
LIBVBUCKET_PUBLIC_API
const char *vbucket_config_get_password(VBUCKET_CONFIG_HANDLE h);
/**
* Get the server at the given index.
*
* @return a string in the form of hostname:port
*/
LIBVBUCKET_PUBLIC_API
const char *vbucket_config_get_server(VBUCKET_CONFIG_HANDLE h, int i);
/**
* Get the CouchDB API endpoint at the given index.
*
* @return a string or NULL.
*/
LIBVBUCKET_PUBLIC_API
const char *vbucket_config_get_couch_api_base(VBUCKET_CONFIG_HANDLE vb, int i);
/**
* Get the REST API endpoint at the given index.
*
* @return a string or NULL.
*/
LIBVBUCKET_PUBLIC_API
const char *vbucket_config_get_rest_api_server(VBUCKET_CONFIG_HANDLE vb, int i);
/**
* Check if the server was used for configuration
*
* @return non-zero for configuration node
*/
LIBVBUCKET_PUBLIC_API
int vbucket_config_is_config_node(VBUCKET_CONFIG_HANDLE h, int i);
/**
* Get the distribution type. Currently can be or "vbucket" (for
* eventually persisted nodes) either "ketama" (for plain memcached
* nodes).
*
* @return a member of VBUCKET_DISTRIBUTION_TYPE enum.
*/
LIBVBUCKET_PUBLIC_API
VBUCKET_DISTRIBUTION_TYPE vbucket_config_get_distribution_type(VBUCKET_CONFIG_HANDLE vb);
/**
* @}
*/
/**
* \addtogroup vb
* @{
*/
/**
* Map given key to server index. It is aware about current distribution
* type.
*
* @param h the vbucket config
* @param key pointer to the beginning of the key
* @param nkey the size of the key
* @param vbucket_id the vbucket identifier when vbucket distribution is
* used or zero otherwise.
* @param server_idx the server index
*
* @return zero on success
*/
LIBVBUCKET_PUBLIC_API
int vbucket_map(VBUCKET_CONFIG_HANDLE h, const void *key, size_t nkey,
int *vbucket_id, int *server_idx);
/**
* Get the vbucket number for the given key.
*
* @param h the vbucket config
* @param key pointer to the beginning of the key
* @param nkey the size of the key
*
* @return a key
*/
LIBVBUCKET_PUBLIC_API
int vbucket_get_vbucket_by_key(VBUCKET_CONFIG_HANDLE h,
const void *key, size_t nkey);
/**
* Get the master server for the given vbucket.
*
* @param h the vbucket config
* @param id the vbucket identifier
*
* @return the server index
*/
LIBVBUCKET_PUBLIC_API
int vbucket_get_master(VBUCKET_CONFIG_HANDLE h, int id);
/**
* Get a given replica for a vbucket.
*
* @param h the vbucket config
* @param id the vbucket id
* @param n the replica number
*
* @return the server ID
*/
LIBVBUCKET_PUBLIC_API
int vbucket_get_replica(VBUCKET_CONFIG_HANDLE h, int id, int n);
/**
* @}
*/
/**
* \addtogroup cfgcmp
* @{
*/
/**
* Compare two vbucket handles.
*
* @param from the source vbucket config
* @param to the destination vbucket config
*
* @return what changed between the "from" config and the "to" config
*/
LIBVBUCKET_PUBLIC_API
VBUCKET_CONFIG_DIFF* vbucket_compare(VBUCKET_CONFIG_HANDLE from,
VBUCKET_CONFIG_HANDLE to);
/**
* Free a vbucket diff.
*
* @param diff the diff to free
*/
LIBVBUCKET_PUBLIC_API
void vbucket_free_diff(VBUCKET_CONFIG_DIFF *diff);
/**
* @}
*/
#ifdef __cplusplus
} // namespace butil
}
#endif
#endif
/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* Copyright 2010 NorthScale, Inc.
*
* Licensed 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.
*/
#ifndef LIBVBUCKET_VISIBILITY_H
#define LIBVBUCKET_VISIBILITY_H 1
#ifdef BUILDING_LIBVBUCKET
#if defined (__SUNPRO_C) && (__SUNPRO_C >= 0x550)
#define LIBVBUCKET_PUBLIC_API __global
#elif defined __GNUC__
#define LIBVBUCKET_PUBLIC_API __attribute__ ((visibility("default")))
#elif defined(_MSC_VER)
#define LIBVBUCKET_PUBLIC_API extern __declspec(dllexport)
#else
/* unknown compiler */
#define LIBVBUCKET_PUBLIC_API
#endif
#else
#if defined(_MSC_VER)
#define LIBVBUCKET_PUBLIC_API extern __declspec(dllimport)
#else
#define LIBVBUCKET_PUBLIC_API
#endif
#endif
#endif /* LIBVBUCKET_VISIBILITY_H */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment