mirror of https://github.com/grpc/grpc.git
commit
08069958fc
136 changed files with 4186 additions and 4380 deletions
@ -1,103 +0,0 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2019 gRPC authors. |
||||
* |
||||
* 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 <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/lib/iomgr/logical_thread.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
DebugOnlyTraceFlag grpc_logical_thread_trace(false, "logical_thread"); |
||||
|
||||
struct CallbackWrapper { |
||||
CallbackWrapper(std::function<void()> cb, const grpc_core::DebugLocation& loc) |
||||
: callback(std::move(cb)), location(loc) {} |
||||
|
||||
MultiProducerSingleConsumerQueue::Node mpscq_node; |
||||
const std::function<void()> callback; |
||||
const DebugLocation location; |
||||
}; |
||||
|
||||
void LogicalThread::Run(std::function<void()> callback, |
||||
const grpc_core::DebugLocation& location) { |
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_logical_thread_trace)) { |
||||
gpr_log(GPR_INFO, "LogicalThread::Run() %p Scheduling callback [%s:%d]", |
||||
this, location.file(), location.line()); |
||||
} |
||||
const size_t prev_size = size_.FetchAdd(1); |
||||
if (prev_size == 0) { |
||||
// There is no other closure executing right now on this logical thread.
|
||||
// Execute this closure immediately.
|
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_logical_thread_trace)) { |
||||
gpr_log(GPR_INFO, " Executing immediately"); |
||||
} |
||||
callback(); |
||||
// Loan this thread to the logical thread and drain the queue.
|
||||
DrainQueue(); |
||||
} else { |
||||
CallbackWrapper* cb_wrapper = |
||||
new CallbackWrapper(std::move(callback), location); |
||||
// There already are closures executing on this logical thread. Simply add
|
||||
// this closure to the queue.
|
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_logical_thread_trace)) { |
||||
gpr_log(GPR_INFO, " Scheduling on queue : item %p", cb_wrapper); |
||||
} |
||||
queue_.Push(&cb_wrapper->mpscq_node); |
||||
} |
||||
} |
||||
|
||||
// The thread that calls this loans itself to the logical thread so as to
|
||||
// execute all the scheduled callback. This is called from within
|
||||
// LogicalThread::Run() after executing a callback immediately, and hence size_
|
||||
// is atleast 1.
|
||||
void LogicalThread::DrainQueue() { |
||||
while (true) { |
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_logical_thread_trace)) { |
||||
gpr_log(GPR_INFO, "LogicalThread::DrainQueue() %p", this); |
||||
} |
||||
size_t prev_size = size_.FetchSub(1); |
||||
// prev_size should be atleast 1 since
|
||||
GPR_DEBUG_ASSERT(prev_size >= 1); |
||||
if (prev_size == 1) { |
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_logical_thread_trace)) { |
||||
gpr_log(GPR_INFO, " Queue Drained"); |
||||
} |
||||
break; |
||||
} |
||||
// There is atleast one callback on the queue. Pop the callback from the
|
||||
// queue and execute it.
|
||||
CallbackWrapper* cb_wrapper = nullptr; |
||||
bool empty_unused; |
||||
while ((cb_wrapper = reinterpret_cast<CallbackWrapper*>( |
||||
queue_.PopAndCheckEnd(&empty_unused))) == nullptr) { |
||||
// This can happen either due to a race condition within the mpscq
|
||||
// implementation or because of a race with Run()
|
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_logical_thread_trace)) { |
||||
gpr_log(GPR_INFO, " Queue returned nullptr, trying again"); |
||||
} |
||||
} |
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_logical_thread_trace)) { |
||||
gpr_log(GPR_INFO, " Running item %p : callback scheduled at [%s:%d]", |
||||
cb_wrapper, cb_wrapper->location.file(), |
||||
cb_wrapper->location.line()); |
||||
} |
||||
cb_wrapper->callback(); |
||||
delete cb_wrapper; |
||||
} |
||||
} |
||||
} // namespace grpc_core
|
@ -1,52 +0,0 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2019 gRPC authors. |
||||
* |
||||
* 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 <grpc/support/port_platform.h> |
||||
|
||||
#include <functional> |
||||
|
||||
#include "src/core/lib/debug/trace.h" |
||||
#include "src/core/lib/gprpp/atomic.h" |
||||
#include "src/core/lib/gprpp/debug_location.h" |
||||
#include "src/core/lib/gprpp/mpscq.h" |
||||
#include "src/core/lib/gprpp/ref_counted.h" |
||||
|
||||
#ifndef GRPC_CORE_LIB_IOMGR_LOGICAL_THREAD_H |
||||
#define GRPC_CORE_LIB_IOMGR_LOGICAL_THREAD_H |
||||
|
||||
namespace grpc_core { |
||||
extern DebugOnlyTraceFlag grpc_logical_thread_trace; |
||||
|
||||
// LogicalThread is a mechanism to schedule callbacks in a synchronized manner.
|
||||
// All callbacks scheduled on a LogicalThread instance will be executed serially
|
||||
// in a borrowed thread. The API provides a FIFO guarantee to the execution of
|
||||
// callbacks scheduled on the thread.
|
||||
class LogicalThread : public RefCounted<LogicalThread> { |
||||
public: |
||||
void Run(std::function<void()> callback, |
||||
const grpc_core::DebugLocation& location); |
||||
|
||||
private: |
||||
void DrainQueue(); |
||||
|
||||
Atomic<size_t> size_{0}; |
||||
MultiProducerSingleConsumerQueue queue_; |
||||
}; |
||||
} /* namespace grpc_core */ |
||||
|
||||
#endif /* GRPC_CORE_LIB_IOMGR_LOGICAL_THREAD_H */ |
@ -0,0 +1,155 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2019 gRPC authors. |
||||
* |
||||
* 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 <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/lib/iomgr/work_serializer.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
DebugOnlyTraceFlag grpc_work_serializer_trace(false, "work_serializer"); |
||||
|
||||
struct CallbackWrapper { |
||||
CallbackWrapper(std::function<void()> cb, const grpc_core::DebugLocation& loc) |
||||
: callback(std::move(cb)), location(loc) {} |
||||
|
||||
MultiProducerSingleConsumerQueue::Node mpscq_node; |
||||
const std::function<void()> callback; |
||||
const DebugLocation location; |
||||
}; |
||||
|
||||
class WorkSerializer::WorkSerializerImpl : public Orphanable { |
||||
public: |
||||
void Run(std::function<void()> callback, |
||||
const grpc_core::DebugLocation& location); |
||||
|
||||
void Orphan() override; |
||||
|
||||
private: |
||||
void DrainQueue(); |
||||
|
||||
// An initial size of 1 keeps track of whether the work serializer has been
|
||||
// orphaned.
|
||||
Atomic<size_t> size_{1}; |
||||
MultiProducerSingleConsumerQueue queue_; |
||||
}; |
||||
|
||||
void WorkSerializer::WorkSerializerImpl::Run( |
||||
std::function<void()> callback, const grpc_core::DebugLocation& location) { |
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_work_serializer_trace)) { |
||||
gpr_log(GPR_INFO, "WorkSerializer::Run() %p Scheduling callback [%s:%d]", |
||||
this, location.file(), location.line()); |
||||
} |
||||
const size_t prev_size = size_.FetchAdd(1); |
||||
// The work serializer should not have been orphaned.
|
||||
GPR_DEBUG_ASSERT(prev_size > 0); |
||||
if (prev_size == 1) { |
||||
// There is no other closure executing right now on this work serializer.
|
||||
// Execute this closure immediately.
|
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_work_serializer_trace)) { |
||||
gpr_log(GPR_INFO, " Executing immediately"); |
||||
} |
||||
callback(); |
||||
// Loan this thread to the work serializer thread and drain the queue.
|
||||
DrainQueue(); |
||||
} else { |
||||
CallbackWrapper* cb_wrapper = |
||||
new CallbackWrapper(std::move(callback), location); |
||||
// There already are closures executing on this work serializer. Simply add
|
||||
// this closure to the queue.
|
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_work_serializer_trace)) { |
||||
gpr_log(GPR_INFO, " Scheduling on queue : item %p", cb_wrapper); |
||||
} |
||||
queue_.Push(&cb_wrapper->mpscq_node); |
||||
} |
||||
} |
||||
|
||||
void WorkSerializer::WorkSerializerImpl::Orphan() { |
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_work_serializer_trace)) { |
||||
gpr_log(GPR_INFO, "WorkSerializer::Orphan() %p", this); |
||||
} |
||||
size_t prev_size = size_.FetchSub(1); |
||||
if (prev_size == 1) { |
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_work_serializer_trace)) { |
||||
gpr_log(GPR_INFO, " Destroying"); |
||||
} |
||||
delete this; |
||||
} |
||||
} |
||||
|
||||
// The thread that calls this loans itself to the work serializer so as to
|
||||
// execute all the scheduled callback. This is called from within
|
||||
// WorkSerializer::Run() after executing a callback immediately, and hence size_
|
||||
// is at least 1.
|
||||
void WorkSerializer::WorkSerializerImpl::DrainQueue() { |
||||
while (true) { |
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_work_serializer_trace)) { |
||||
gpr_log(GPR_INFO, "WorkSerializer::DrainQueue() %p", this); |
||||
} |
||||
size_t prev_size = size_.FetchSub(1); |
||||
GPR_DEBUG_ASSERT(prev_size >= 1); |
||||
// It is possible that while draining the queue, one of the callbacks ended
|
||||
// up orphaning the work serializer. In that case, delete the object.
|
||||
if (prev_size == 1) { |
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_work_serializer_trace)) { |
||||
gpr_log(GPR_INFO, " Queue Drained. Destroying"); |
||||
} |
||||
delete this; |
||||
return; |
||||
} |
||||
if (prev_size == 2) { |
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_work_serializer_trace)) { |
||||
gpr_log(GPR_INFO, " Queue Drained"); |
||||
} |
||||
return; |
||||
} |
||||
// There is at least one callback on the queue. Pop the callback from the
|
||||
// queue and execute it.
|
||||
CallbackWrapper* cb_wrapper = nullptr; |
||||
bool empty_unused; |
||||
while ((cb_wrapper = reinterpret_cast<CallbackWrapper*>( |
||||
queue_.PopAndCheckEnd(&empty_unused))) == nullptr) { |
||||
// This can happen either due to a race condition within the mpscq
|
||||
// implementation or because of a race with Run()
|
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_work_serializer_trace)) { |
||||
gpr_log(GPR_INFO, " Queue returned nullptr, trying again"); |
||||
} |
||||
} |
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_work_serializer_trace)) { |
||||
gpr_log(GPR_INFO, " Running item %p : callback scheduled at [%s:%d]", |
||||
cb_wrapper, cb_wrapper->location.file(), |
||||
cb_wrapper->location.line()); |
||||
} |
||||
cb_wrapper->callback(); |
||||
delete cb_wrapper; |
||||
} |
||||
} |
||||
|
||||
// WorkSerializer
|
||||
|
||||
WorkSerializer::WorkSerializer() |
||||
: impl_(MakeOrphanable<WorkSerializerImpl>()) {} |
||||
|
||||
WorkSerializer::~WorkSerializer() {} |
||||
|
||||
void WorkSerializer::Run(std::function<void()> callback, |
||||
const grpc_core::DebugLocation& location) { |
||||
impl_->Run(std::move(callback), location); |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,65 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2019 gRPC authors. |
||||
* |
||||
* 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 <grpc/support/port_platform.h> |
||||
|
||||
#include <functional> |
||||
|
||||
#include "src/core/lib/debug/trace.h" |
||||
#include "src/core/lib/gprpp/atomic.h" |
||||
#include "src/core/lib/gprpp/debug_location.h" |
||||
#include "src/core/lib/gprpp/mpscq.h" |
||||
#include "src/core/lib/gprpp/orphanable.h" |
||||
#include "src/core/lib/gprpp/ref_counted.h" |
||||
#include "src/core/lib/iomgr/exec_ctx.h" |
||||
|
||||
#ifndef GRPC_CORE_LIB_IOMGR_WORK_SERIALIZER_H |
||||
#define GRPC_CORE_LIB_IOMGR_WORK_SERIALIZER_H |
||||
|
||||
namespace grpc_core { |
||||
|
||||
// WorkSerializer is a mechanism to schedule callbacks in a synchronized manner.
|
||||
// All callbacks scheduled on a WorkSerializer instance will be executed
|
||||
// serially in a borrowed thread. The API provides a FIFO guarantee to the
|
||||
// execution of callbacks scheduled on the thread.
|
||||
// When a thread calls Run() with a callback, the thread is considered borrowed.
|
||||
// The callback might run inline, or it might run asynchronously in a different
|
||||
// thread that is already inside of Run(). If the callback runs directly inline,
|
||||
// other callbacks from other threads might also be executed before Run()
|
||||
// returns. Since an arbitrary set of callbacks might be executed when Run() is
|
||||
// called, generally no locks should be held while calling Run().
|
||||
class WorkSerializer { |
||||
public: |
||||
WorkSerializer(); |
||||
|
||||
~WorkSerializer(); |
||||
|
||||
// TODO(yashkt): Replace grpc_core::DebugLocation with absl::SourceLocation
|
||||
// once we can start using it directly.
|
||||
void Run(std::function<void()> callback, |
||||
const grpc_core::DebugLocation& location); |
||||
|
||||
private: |
||||
class WorkSerializerImpl; |
||||
|
||||
OrphanablePtr<WorkSerializerImpl> impl_; |
||||
}; |
||||
|
||||
} /* namespace grpc_core */ |
||||
|
||||
#endif /* GRPC_CORE_LIB_IOMGR_WORK_SERIALIZER_H */ |
@ -1,94 +0,0 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2015 gRPC authors. |
||||
* |
||||
* 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 <grpc/support/port_platform.h> |
||||
|
||||
#include <inttypes.h> |
||||
#include <string.h> |
||||
|
||||
#include <grpc/support/alloc.h> |
||||
#include <grpc/support/log.h> |
||||
#include <grpc/support/string_util.h> |
||||
|
||||
#include "src/core/lib/json/json.h" |
||||
|
||||
grpc_json* grpc_json_create(grpc_json_type type) { |
||||
grpc_json* json = static_cast<grpc_json*>(gpr_zalloc(sizeof(*json))); |
||||
json->type = type; |
||||
|
||||
return json; |
||||
} |
||||
|
||||
void grpc_json_destroy(grpc_json* json) { |
||||
if (json == nullptr) return; |
||||
while (json->child) { |
||||
grpc_json_destroy(json->child); |
||||
} |
||||
if (json->next) { |
||||
json->next->prev = json->prev; |
||||
} |
||||
if (json->prev) { |
||||
json->prev->next = json->next; |
||||
} else if (json->parent) { |
||||
json->parent->child = json->next; |
||||
} |
||||
if (json->owns_value) { |
||||
gpr_free((void*)json->value); |
||||
} |
||||
gpr_free(json); |
||||
} |
||||
|
||||
grpc_json* grpc_json_link_child(grpc_json* parent, grpc_json* child, |
||||
grpc_json* sibling) { |
||||
// link child up to parent
|
||||
child->parent = parent; |
||||
// first child case.
|
||||
if (parent->child == nullptr) { |
||||
GPR_ASSERT(sibling == nullptr); |
||||
parent->child = child; |
||||
return child; |
||||
} |
||||
if (sibling == nullptr) { |
||||
sibling = parent->child; |
||||
} |
||||
// always find the right most sibling.
|
||||
while (sibling->next != nullptr) { |
||||
sibling = sibling->next; |
||||
} |
||||
sibling->next = child; |
||||
return child; |
||||
} |
||||
|
||||
grpc_json* grpc_json_create_child(grpc_json* sibling, grpc_json* parent, |
||||
const char* key, const char* value, |
||||
grpc_json_type type, bool owns_value) { |
||||
grpc_json* child = grpc_json_create(type); |
||||
grpc_json_link_child(parent, child, sibling); |
||||
child->owns_value = owns_value; |
||||
child->value = value; |
||||
child->key = key; |
||||
return child; |
||||
} |
||||
|
||||
grpc_json* grpc_json_add_number_string_child(grpc_json* parent, grpc_json* it, |
||||
const char* name, int64_t num) { |
||||
char* num_str; |
||||
gpr_asprintf(&num_str, "%" PRId64, num); |
||||
return grpc_json_create_child(it, parent, name, num_str, GRPC_JSON_STRING, |
||||
true); |
||||
} |
@ -1,808 +0,0 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2015-2016 gRPC authors. |
||||
* |
||||
* 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 <grpc/support/port_platform.h> |
||||
|
||||
#include <string.h> |
||||
|
||||
#include <grpc/support/log.h> |
||||
|
||||
#include "src/core/lib/json/json.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
namespace { |
||||
|
||||
class JsonReader { |
||||
public: |
||||
enum class Status { |
||||
GRPC_JSON_DONE, /* The parser finished successfully. */ |
||||
GRPC_JSON_PARSE_ERROR, /* The parser found an error in the json stream. */ |
||||
GRPC_JSON_INTERNAL_ERROR /* The parser got an internal error. */ |
||||
}; |
||||
|
||||
static Status Parse(StringView input, Json* output); |
||||
|
||||
private: |
||||
enum class State { |
||||
GRPC_JSON_STATE_OBJECT_KEY_BEGIN, |
||||
GRPC_JSON_STATE_OBJECT_KEY_STRING, |
||||
GRPC_JSON_STATE_OBJECT_KEY_END, |
||||
GRPC_JSON_STATE_VALUE_BEGIN, |
||||
GRPC_JSON_STATE_VALUE_STRING, |
||||
GRPC_JSON_STATE_STRING_ESCAPE, |
||||
GRPC_JSON_STATE_STRING_ESCAPE_U1, |
||||
GRPC_JSON_STATE_STRING_ESCAPE_U2, |
||||
GRPC_JSON_STATE_STRING_ESCAPE_U3, |
||||
GRPC_JSON_STATE_STRING_ESCAPE_U4, |
||||
GRPC_JSON_STATE_VALUE_NUMBER, |
||||
GRPC_JSON_STATE_VALUE_NUMBER_WITH_DECIMAL, |
||||
GRPC_JSON_STATE_VALUE_NUMBER_ZERO, |
||||
GRPC_JSON_STATE_VALUE_NUMBER_DOT, |
||||
GRPC_JSON_STATE_VALUE_NUMBER_E, |
||||
GRPC_JSON_STATE_VALUE_NUMBER_EPM, |
||||
GRPC_JSON_STATE_VALUE_TRUE_R, |
||||
GRPC_JSON_STATE_VALUE_TRUE_U, |
||||
GRPC_JSON_STATE_VALUE_TRUE_E, |
||||
GRPC_JSON_STATE_VALUE_FALSE_A, |
||||
GRPC_JSON_STATE_VALUE_FALSE_L, |
||||
GRPC_JSON_STATE_VALUE_FALSE_S, |
||||
GRPC_JSON_STATE_VALUE_FALSE_E, |
||||
GRPC_JSON_STATE_VALUE_NULL_U, |
||||
GRPC_JSON_STATE_VALUE_NULL_L1, |
||||
GRPC_JSON_STATE_VALUE_NULL_L2, |
||||
GRPC_JSON_STATE_VALUE_END, |
||||
GRPC_JSON_STATE_END |
||||
}; |
||||
|
||||
/* The first non-unicode value is 0x110000. But let's pick
|
||||
* a value high enough to start our error codes from. These |
||||
* values are safe to return from the read_char function. |
||||
*/ |
||||
static constexpr uint32_t GRPC_JSON_READ_CHAR_EOF = 0x7ffffff0; |
||||
|
||||
explicit JsonReader(StringView input) |
||||
: input_(reinterpret_cast<const uint8_t*>(input.data())), |
||||
remaining_input_(input.size()) {} |
||||
|
||||
Status Run(); |
||||
uint32_t ReadChar(); |
||||
bool IsComplete(); |
||||
|
||||
void StringAddChar(uint32_t c); |
||||
void StringAddUtf32(uint32_t c); |
||||
|
||||
Json* CreateAndLinkValue(); |
||||
void StartContainer(Json::Type type); |
||||
void EndContainer(); |
||||
void SetKey(); |
||||
void SetString(); |
||||
bool SetNumber(); |
||||
void SetTrue(); |
||||
void SetFalse(); |
||||
void SetNull(); |
||||
|
||||
const uint8_t* input_; |
||||
size_t remaining_input_; |
||||
|
||||
State state_ = State::GRPC_JSON_STATE_VALUE_BEGIN; |
||||
bool escaped_string_was_key_ = false; |
||||
bool container_just_begun_ = false; |
||||
uint16_t unicode_char_ = 0; |
||||
uint16_t unicode_high_surrogate_ = 0; |
||||
bool duplicate_key_found_ = false; |
||||
|
||||
Json root_value_; |
||||
std::vector<Json*> stack_; |
||||
|
||||
std::string key_; |
||||
std::string string_; |
||||
}; |
||||
|
||||
void JsonReader::StringAddChar(uint32_t c) { |
||||
string_.push_back(static_cast<uint8_t>(c)); |
||||
} |
||||
|
||||
void JsonReader::StringAddUtf32(uint32_t c) { |
||||
if (c <= 0x7f) { |
||||
StringAddChar(c); |
||||
} else if (c <= 0x7ff) { |
||||
uint32_t b1 = 0xc0 | ((c >> 6) & 0x1f); |
||||
uint32_t b2 = 0x80 | (c & 0x3f); |
||||
StringAddChar(b1); |
||||
StringAddChar(b2); |
||||
} else if (c <= 0xffff) { |
||||
uint32_t b1 = 0xe0 | ((c >> 12) & 0x0f); |
||||
uint32_t b2 = 0x80 | ((c >> 6) & 0x3f); |
||||
uint32_t b3 = 0x80 | (c & 0x3f); |
||||
StringAddChar(b1); |
||||
StringAddChar(b2); |
||||
StringAddChar(b3); |
||||
} else if (c <= 0x1fffff) { |
||||
uint32_t b1 = 0xf0 | ((c >> 18) & 0x07); |
||||
uint32_t b2 = 0x80 | ((c >> 12) & 0x3f); |
||||
uint32_t b3 = 0x80 | ((c >> 6) & 0x3f); |
||||
uint32_t b4 = 0x80 | (c & 0x3f); |
||||
StringAddChar(b1); |
||||
StringAddChar(b2); |
||||
StringAddChar(b3); |
||||
StringAddChar(b4); |
||||
} |
||||
} |
||||
|
||||
uint32_t JsonReader::ReadChar() { |
||||
if (remaining_input_ == 0) return GRPC_JSON_READ_CHAR_EOF; |
||||
const uint32_t r = *input_++; |
||||
--remaining_input_; |
||||
if (r == 0) { |
||||
remaining_input_ = 0; |
||||
return GRPC_JSON_READ_CHAR_EOF; |
||||
} |
||||
return r; |
||||
} |
||||
|
||||
Json* JsonReader::CreateAndLinkValue() { |
||||
Json* value; |
||||
if (stack_.empty()) { |
||||
value = &root_value_; |
||||
} else { |
||||
Json* parent = stack_.back(); |
||||
if (parent->type() == Json::Type::OBJECT) { |
||||
if (parent->object_value().find(key_) != parent->object_value().end()) { |
||||
duplicate_key_found_ = true; |
||||
} |
||||
value = &(*parent->mutable_object())[std::move(key_)]; |
||||
} else { |
||||
GPR_ASSERT(parent->type() == Json::Type::ARRAY); |
||||
parent->mutable_array()->emplace_back(); |
||||
value = &parent->mutable_array()->back(); |
||||
} |
||||
} |
||||
return value; |
||||
} |
||||
|
||||
void JsonReader::StartContainer(Json::Type type) { |
||||
Json* value = CreateAndLinkValue(); |
||||
if (type == Json::Type::OBJECT) { |
||||
*value = Json::Object(); |
||||
} else { |
||||
GPR_ASSERT(type == Json::Type::ARRAY); |
||||
*value = Json::Array(); |
||||
} |
||||
stack_.push_back(value); |
||||
} |
||||
|
||||
void JsonReader::EndContainer() { |
||||
GPR_ASSERT(!stack_.empty()); |
||||
stack_.pop_back(); |
||||
} |
||||
|
||||
void JsonReader::SetKey() { |
||||
key_ = std::move(string_); |
||||
string_.clear(); |
||||
} |
||||
|
||||
void JsonReader::SetString() { |
||||
Json* value = CreateAndLinkValue(); |
||||
*value = std::move(string_); |
||||
string_.clear(); |
||||
} |
||||
|
||||
bool JsonReader::SetNumber() { |
||||
Json* value = CreateAndLinkValue(); |
||||
*value = Json(std::move(string_), /*is_number=*/true); |
||||
string_.clear(); |
||||
return true; |
||||
} |
||||
|
||||
void JsonReader::SetTrue() { |
||||
Json* value = CreateAndLinkValue(); |
||||
*value = true; |
||||
string_.clear(); |
||||
} |
||||
|
||||
void JsonReader::SetFalse() { |
||||
Json* value = CreateAndLinkValue(); |
||||
*value = false; |
||||
string_.clear(); |
||||
} |
||||
|
||||
void JsonReader::SetNull() { CreateAndLinkValue(); } |
||||
|
||||
bool JsonReader::IsComplete() { |
||||
return (stack_.empty() && (state_ == State::GRPC_JSON_STATE_END || |
||||
state_ == State::GRPC_JSON_STATE_VALUE_END)); |
||||
} |
||||
|
||||
/* Call this function to start parsing the input. It will return the following:
|
||||
* . GRPC_JSON_DONE if the input got eof, and the parsing finished |
||||
* successfully. |
||||
* . GRPC_JSON_PARSE_ERROR if the input was somehow invalid. |
||||
* . GRPC_JSON_INTERNAL_ERROR if the parser somehow ended into an invalid |
||||
* internal state. |
||||
*/ |
||||
JsonReader::Status JsonReader::Run() { |
||||
uint32_t c; |
||||
|
||||
/* This state-machine is a strict implementation of ECMA-404 */ |
||||
while (true) { |
||||
c = ReadChar(); |
||||
switch (c) { |
||||
/* Let's process the error case first. */ |
||||
case GRPC_JSON_READ_CHAR_EOF: |
||||
if (IsComplete()) { |
||||
return Status::GRPC_JSON_DONE; |
||||
} else { |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
break; |
||||
|
||||
/* Processing whitespaces. */ |
||||
case ' ': |
||||
case '\t': |
||||
case '\n': |
||||
case '\r': |
||||
switch (state_) { |
||||
case State::GRPC_JSON_STATE_OBJECT_KEY_BEGIN: |
||||
case State::GRPC_JSON_STATE_OBJECT_KEY_END: |
||||
case State::GRPC_JSON_STATE_VALUE_BEGIN: |
||||
case State::GRPC_JSON_STATE_VALUE_END: |
||||
case State::GRPC_JSON_STATE_END: |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_OBJECT_KEY_STRING: |
||||
case State::GRPC_JSON_STATE_VALUE_STRING: |
||||
if (c != ' ') return Status::GRPC_JSON_PARSE_ERROR; |
||||
if (unicode_high_surrogate_ != 0) { |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
StringAddChar(c); |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_NUMBER: |
||||
case State::GRPC_JSON_STATE_VALUE_NUMBER_WITH_DECIMAL: |
||||
case State::GRPC_JSON_STATE_VALUE_NUMBER_ZERO: |
||||
case State::GRPC_JSON_STATE_VALUE_NUMBER_EPM: |
||||
if (!SetNumber()) return Status::GRPC_JSON_PARSE_ERROR; |
||||
state_ = State::GRPC_JSON_STATE_VALUE_END; |
||||
break; |
||||
|
||||
default: |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
break; |
||||
|
||||
/* Value, object or array terminations. */ |
||||
case ',': |
||||
case '}': |
||||
case ']': |
||||
switch (state_) { |
||||
case State::GRPC_JSON_STATE_OBJECT_KEY_STRING: |
||||
case State::GRPC_JSON_STATE_VALUE_STRING: |
||||
if (unicode_high_surrogate_ != 0) { |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
StringAddChar(c); |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_NUMBER: |
||||
case State::GRPC_JSON_STATE_VALUE_NUMBER_WITH_DECIMAL: |
||||
case State::GRPC_JSON_STATE_VALUE_NUMBER_ZERO: |
||||
case State::GRPC_JSON_STATE_VALUE_NUMBER_EPM: |
||||
if (stack_.empty()) { |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} else if (c == '}' && |
||||
stack_.back()->type() != Json::Type::OBJECT) { |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} else if (c == ']' && stack_.back()->type() != Json::Type::ARRAY) { |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
if (!SetNumber()) return Status::GRPC_JSON_PARSE_ERROR; |
||||
state_ = State::GRPC_JSON_STATE_VALUE_END; |
||||
/* The missing break here is intentional. */ |
||||
/* fallthrough */ |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_END: |
||||
case State::GRPC_JSON_STATE_OBJECT_KEY_BEGIN: |
||||
case State::GRPC_JSON_STATE_VALUE_BEGIN: |
||||
if (c == ',') { |
||||
if (state_ != State::GRPC_JSON_STATE_VALUE_END) { |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
if (!stack_.empty() && |
||||
stack_.back()->type() == Json::Type::OBJECT) { |
||||
state_ = State::GRPC_JSON_STATE_OBJECT_KEY_BEGIN; |
||||
} else if (!stack_.empty() && |
||||
stack_.back()->type() == Json::Type::ARRAY) { |
||||
state_ = State::GRPC_JSON_STATE_VALUE_BEGIN; |
||||
} else { |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
} else { |
||||
if (stack_.empty()) { |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
if (c == '}' && stack_.back()->type() != Json::Type::OBJECT) { |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
if (c == '}' && |
||||
state_ == State::GRPC_JSON_STATE_OBJECT_KEY_BEGIN && |
||||
!container_just_begun_) { |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
if (c == ']' && stack_.back()->type() != Json::Type::ARRAY) { |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
if (c == ']' && state_ == State::GRPC_JSON_STATE_VALUE_BEGIN && |
||||
!container_just_begun_) { |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
state_ = State::GRPC_JSON_STATE_VALUE_END; |
||||
EndContainer(); |
||||
if (stack_.empty()) { |
||||
state_ = State::GRPC_JSON_STATE_END; |
||||
} |
||||
} |
||||
break; |
||||
|
||||
default: |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
break; |
||||
|
||||
/* In-string escaping. */ |
||||
case '\\': |
||||
switch (state_) { |
||||
case State::GRPC_JSON_STATE_OBJECT_KEY_STRING: |
||||
escaped_string_was_key_ = true; |
||||
state_ = State::GRPC_JSON_STATE_STRING_ESCAPE; |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_STRING: |
||||
escaped_string_was_key_ = false; |
||||
state_ = State::GRPC_JSON_STATE_STRING_ESCAPE; |
||||
break; |
||||
|
||||
/* This is the \\ case. */ |
||||
case State::GRPC_JSON_STATE_STRING_ESCAPE: |
||||
if (unicode_high_surrogate_ != 0) |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
StringAddChar('\\'); |
||||
if (escaped_string_was_key_) { |
||||
state_ = State::GRPC_JSON_STATE_OBJECT_KEY_STRING; |
||||
} else { |
||||
state_ = State::GRPC_JSON_STATE_VALUE_STRING; |
||||
} |
||||
break; |
||||
|
||||
default: |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
break; |
||||
|
||||
default: |
||||
container_just_begun_ = false; |
||||
switch (state_) { |
||||
case State::GRPC_JSON_STATE_OBJECT_KEY_BEGIN: |
||||
if (c != '"') return Status::GRPC_JSON_PARSE_ERROR; |
||||
state_ = State::GRPC_JSON_STATE_OBJECT_KEY_STRING; |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_OBJECT_KEY_STRING: |
||||
if (unicode_high_surrogate_ != 0) { |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
if (c == '"') { |
||||
state_ = State::GRPC_JSON_STATE_OBJECT_KEY_END; |
||||
SetKey(); |
||||
} else { |
||||
if (c < 32) return Status::GRPC_JSON_PARSE_ERROR; |
||||
StringAddChar(c); |
||||
} |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_STRING: |
||||
if (unicode_high_surrogate_ != 0) { |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
if (c == '"') { |
||||
state_ = State::GRPC_JSON_STATE_VALUE_END; |
||||
SetString(); |
||||
} else { |
||||
if (c < 32) return Status::GRPC_JSON_PARSE_ERROR; |
||||
StringAddChar(c); |
||||
} |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_OBJECT_KEY_END: |
||||
if (c != ':') return Status::GRPC_JSON_PARSE_ERROR; |
||||
state_ = State::GRPC_JSON_STATE_VALUE_BEGIN; |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_BEGIN: |
||||
switch (c) { |
||||
case 't': |
||||
state_ = State::GRPC_JSON_STATE_VALUE_TRUE_R; |
||||
break; |
||||
|
||||
case 'f': |
||||
state_ = State::GRPC_JSON_STATE_VALUE_FALSE_A; |
||||
break; |
||||
|
||||
case 'n': |
||||
state_ = State::GRPC_JSON_STATE_VALUE_NULL_U; |
||||
break; |
||||
|
||||
case '"': |
||||
state_ = State::GRPC_JSON_STATE_VALUE_STRING; |
||||
break; |
||||
|
||||
case '0': |
||||
StringAddChar(c); |
||||
state_ = State::GRPC_JSON_STATE_VALUE_NUMBER_ZERO; |
||||
break; |
||||
|
||||
case '1': |
||||
case '2': |
||||
case '3': |
||||
case '4': |
||||
case '5': |
||||
case '6': |
||||
case '7': |
||||
case '8': |
||||
case '9': |
||||
case '-': |
||||
StringAddChar(c); |
||||
state_ = State::GRPC_JSON_STATE_VALUE_NUMBER; |
||||
break; |
||||
|
||||
case '{': |
||||
container_just_begun_ = true; |
||||
StartContainer(Json::Type::OBJECT); |
||||
state_ = State::GRPC_JSON_STATE_OBJECT_KEY_BEGIN; |
||||
break; |
||||
|
||||
case '[': |
||||
container_just_begun_ = true; |
||||
StartContainer(Json::Type::ARRAY); |
||||
break; |
||||
default: |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_STRING_ESCAPE: |
||||
if (escaped_string_was_key_) { |
||||
state_ = State::GRPC_JSON_STATE_OBJECT_KEY_STRING; |
||||
} else { |
||||
state_ = State::GRPC_JSON_STATE_VALUE_STRING; |
||||
} |
||||
if (unicode_high_surrogate_ && c != 'u') { |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
switch (c) { |
||||
case '"': |
||||
case '/': |
||||
StringAddChar(c); |
||||
break; |
||||
case 'b': |
||||
StringAddChar('\b'); |
||||
break; |
||||
case 'f': |
||||
StringAddChar('\f'); |
||||
break; |
||||
case 'n': |
||||
StringAddChar('\n'); |
||||
break; |
||||
case 'r': |
||||
StringAddChar('\r'); |
||||
break; |
||||
case 't': |
||||
StringAddChar('\t'); |
||||
break; |
||||
case 'u': |
||||
state_ = State::GRPC_JSON_STATE_STRING_ESCAPE_U1; |
||||
unicode_char_ = 0; |
||||
break; |
||||
default: |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_STRING_ESCAPE_U1: |
||||
case State::GRPC_JSON_STATE_STRING_ESCAPE_U2: |
||||
case State::GRPC_JSON_STATE_STRING_ESCAPE_U3: |
||||
case State::GRPC_JSON_STATE_STRING_ESCAPE_U4: |
||||
if ((c >= '0') && (c <= '9')) { |
||||
c -= '0'; |
||||
} else if ((c >= 'A') && (c <= 'F')) { |
||||
c -= 'A' - 10; |
||||
} else if ((c >= 'a') && (c <= 'f')) { |
||||
c -= 'a' - 10; |
||||
} else { |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
unicode_char_ = static_cast<uint16_t>(unicode_char_ << 4); |
||||
unicode_char_ = static_cast<uint16_t>(unicode_char_ | c); |
||||
|
||||
switch (state_) { |
||||
case State::GRPC_JSON_STATE_STRING_ESCAPE_U1: |
||||
state_ = State::GRPC_JSON_STATE_STRING_ESCAPE_U2; |
||||
break; |
||||
case State::GRPC_JSON_STATE_STRING_ESCAPE_U2: |
||||
state_ = State::GRPC_JSON_STATE_STRING_ESCAPE_U3; |
||||
break; |
||||
case State::GRPC_JSON_STATE_STRING_ESCAPE_U3: |
||||
state_ = State::GRPC_JSON_STATE_STRING_ESCAPE_U4; |
||||
break; |
||||
case State::GRPC_JSON_STATE_STRING_ESCAPE_U4: |
||||
/* See grpc_json_writer_escape_string to have a description
|
||||
* of what's going on here. |
||||
*/ |
||||
if ((unicode_char_ & 0xfc00) == 0xd800) { |
||||
/* high surrogate utf-16 */ |
||||
if (unicode_high_surrogate_ != 0) |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
unicode_high_surrogate_ = unicode_char_; |
||||
} else if ((unicode_char_ & 0xfc00) == 0xdc00) { |
||||
/* low surrogate utf-16 */ |
||||
uint32_t utf32; |
||||
if (unicode_high_surrogate_ == 0) |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
utf32 = 0x10000; |
||||
utf32 += static_cast<uint32_t>( |
||||
(unicode_high_surrogate_ - 0xd800) * 0x400); |
||||
utf32 += static_cast<uint32_t>(unicode_char_ - 0xdc00); |
||||
StringAddUtf32(utf32); |
||||
unicode_high_surrogate_ = 0; |
||||
} else { |
||||
/* anything else */ |
||||
if (unicode_high_surrogate_ != 0) |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
StringAddUtf32(unicode_char_); |
||||
} |
||||
if (escaped_string_was_key_) { |
||||
state_ = State::GRPC_JSON_STATE_OBJECT_KEY_STRING; |
||||
} else { |
||||
state_ = State::GRPC_JSON_STATE_VALUE_STRING; |
||||
} |
||||
break; |
||||
default: |
||||
GPR_UNREACHABLE_CODE(return Status::GRPC_JSON_INTERNAL_ERROR); |
||||
} |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_NUMBER: |
||||
StringAddChar(c); |
||||
switch (c) { |
||||
case '0': |
||||
case '1': |
||||
case '2': |
||||
case '3': |
||||
case '4': |
||||
case '5': |
||||
case '6': |
||||
case '7': |
||||
case '8': |
||||
case '9': |
||||
break; |
||||
case 'e': |
||||
case 'E': |
||||
state_ = State::GRPC_JSON_STATE_VALUE_NUMBER_E; |
||||
break; |
||||
case '.': |
||||
state_ = State::GRPC_JSON_STATE_VALUE_NUMBER_DOT; |
||||
break; |
||||
default: |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_NUMBER_WITH_DECIMAL: |
||||
StringAddChar(c); |
||||
switch (c) { |
||||
case '0': |
||||
case '1': |
||||
case '2': |
||||
case '3': |
||||
case '4': |
||||
case '5': |
||||
case '6': |
||||
case '7': |
||||
case '8': |
||||
case '9': |
||||
break; |
||||
case 'e': |
||||
case 'E': |
||||
state_ = State::GRPC_JSON_STATE_VALUE_NUMBER_E; |
||||
break; |
||||
default: |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_NUMBER_ZERO: |
||||
if (c != '.') return Status::GRPC_JSON_PARSE_ERROR; |
||||
StringAddChar(c); |
||||
state_ = State::GRPC_JSON_STATE_VALUE_NUMBER_DOT; |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_NUMBER_DOT: |
||||
StringAddChar(c); |
||||
switch (c) { |
||||
case '0': |
||||
case '1': |
||||
case '2': |
||||
case '3': |
||||
case '4': |
||||
case '5': |
||||
case '6': |
||||
case '7': |
||||
case '8': |
||||
case '9': |
||||
state_ = State::GRPC_JSON_STATE_VALUE_NUMBER_WITH_DECIMAL; |
||||
break; |
||||
default: |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_NUMBER_E: |
||||
StringAddChar(c); |
||||
switch (c) { |
||||
case '0': |
||||
case '1': |
||||
case '2': |
||||
case '3': |
||||
case '4': |
||||
case '5': |
||||
case '6': |
||||
case '7': |
||||
case '8': |
||||
case '9': |
||||
case '+': |
||||
case '-': |
||||
state_ = State::GRPC_JSON_STATE_VALUE_NUMBER_EPM; |
||||
break; |
||||
default: |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_NUMBER_EPM: |
||||
StringAddChar(c); |
||||
switch (c) { |
||||
case '0': |
||||
case '1': |
||||
case '2': |
||||
case '3': |
||||
case '4': |
||||
case '5': |
||||
case '6': |
||||
case '7': |
||||
case '8': |
||||
case '9': |
||||
break; |
||||
default: |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_TRUE_R: |
||||
if (c != 'r') return Status::GRPC_JSON_PARSE_ERROR; |
||||
state_ = State::GRPC_JSON_STATE_VALUE_TRUE_U; |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_TRUE_U: |
||||
if (c != 'u') return Status::GRPC_JSON_PARSE_ERROR; |
||||
state_ = State::GRPC_JSON_STATE_VALUE_TRUE_E; |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_TRUE_E: |
||||
if (c != 'e') return Status::GRPC_JSON_PARSE_ERROR; |
||||
SetTrue(); |
||||
state_ = State::GRPC_JSON_STATE_VALUE_END; |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_FALSE_A: |
||||
if (c != 'a') return Status::GRPC_JSON_PARSE_ERROR; |
||||
state_ = State::GRPC_JSON_STATE_VALUE_FALSE_L; |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_FALSE_L: |
||||
if (c != 'l') return Status::GRPC_JSON_PARSE_ERROR; |
||||
state_ = State::GRPC_JSON_STATE_VALUE_FALSE_S; |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_FALSE_S: |
||||
if (c != 's') return Status::GRPC_JSON_PARSE_ERROR; |
||||
state_ = State::GRPC_JSON_STATE_VALUE_FALSE_E; |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_FALSE_E: |
||||
if (c != 'e') return Status::GRPC_JSON_PARSE_ERROR; |
||||
SetFalse(); |
||||
state_ = State::GRPC_JSON_STATE_VALUE_END; |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_NULL_U: |
||||
if (c != 'u') return Status::GRPC_JSON_PARSE_ERROR; |
||||
state_ = State::GRPC_JSON_STATE_VALUE_NULL_L1; |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_NULL_L1: |
||||
if (c != 'l') return Status::GRPC_JSON_PARSE_ERROR; |
||||
state_ = State::GRPC_JSON_STATE_VALUE_NULL_L2; |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_VALUE_NULL_L2: |
||||
if (c != 'l') return Status::GRPC_JSON_PARSE_ERROR; |
||||
SetNull(); |
||||
state_ = State::GRPC_JSON_STATE_VALUE_END; |
||||
break; |
||||
|
||||
/* All of the VALUE_END cases are handled in the specialized case
|
||||
* above. */ |
||||
case State::GRPC_JSON_STATE_VALUE_END: |
||||
switch (c) { |
||||
case ',': |
||||
case '}': |
||||
case ']': |
||||
GPR_UNREACHABLE_CODE(return Status::GRPC_JSON_INTERNAL_ERROR); |
||||
break; |
||||
|
||||
default: |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
break; |
||||
|
||||
case State::GRPC_JSON_STATE_END: |
||||
return Status::GRPC_JSON_PARSE_ERROR; |
||||
} |
||||
} |
||||
} |
||||
|
||||
GPR_UNREACHABLE_CODE(return Status::GRPC_JSON_INTERNAL_ERROR); |
||||
} |
||||
|
||||
JsonReader::Status JsonReader::Parse(StringView input, Json* output) { |
||||
JsonReader reader(input); |
||||
Status status = reader.Run(); |
||||
if (reader.duplicate_key_found_) status = Status::GRPC_JSON_PARSE_ERROR; |
||||
if (status == Status::GRPC_JSON_DONE) { |
||||
*output = std::move(reader.root_value_); |
||||
} |
||||
return status; |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
Json Json::Parse(StringView json_str, grpc_error** error) { |
||||
Json value; |
||||
JsonReader::Status status = JsonReader::Parse(json_str, &value); |
||||
if (status == JsonReader::Status::GRPC_JSON_PARSE_ERROR) { |
||||
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON parse error"); |
||||
} else if (status == JsonReader::Status::GRPC_JSON_INTERNAL_ERROR) { |
||||
*error = |
||||
GRPC_ERROR_CREATE_FROM_STATIC_STRING("internal error in JSON parser"); |
||||
} |
||||
return value; |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -1,336 +0,0 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2015 gRPC authors. |
||||
* |
||||
* 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 <grpc/support/port_platform.h> |
||||
|
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
|
||||
#include <grpc/support/alloc.h> |
||||
#include <grpc/support/log.h> |
||||
|
||||
#include "src/core/lib/json/json.h" |
||||
|
||||
#include "src/core/lib/gprpp/string_view.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
namespace { |
||||
|
||||
/* The idea of the writer is basically symmetrical of the reader. While the
|
||||
* reader emits various calls to your code, the writer takes basically the |
||||
* same calls and emit json out of it. It doesn't try to make any check on |
||||
* the order of the calls you do on it. Meaning you can theorically force |
||||
* it to generate invalid json. |
||||
* |
||||
* Also, unlike the reader, the writer expects UTF-8 encoded input strings. |
||||
* These strings will be UTF-8 validated, and any invalid character will |
||||
* cut the conversion short, before any invalid UTF-8 sequence, thus forming |
||||
* a valid UTF-8 string overall. |
||||
*/ |
||||
class JsonWriter { |
||||
public: |
||||
static std::string Dump(const Json& value, int indent); |
||||
|
||||
private: |
||||
explicit JsonWriter(int indent) : indent_(indent) {} |
||||
|
||||
void OutputCheck(size_t needed); |
||||
void OutputChar(char c); |
||||
void OutputString(const StringView str); |
||||
void OutputIndent(); |
||||
void ValueEnd(); |
||||
void EscapeUtf16(uint16_t utf16); |
||||
void EscapeString(const std::string& string); |
||||
void ContainerBegins(Json::Type type); |
||||
void ContainerEnds(Json::Type type); |
||||
void ObjectKey(const std::string& string); |
||||
void ValueRaw(const std::string& string); |
||||
void ValueString(const std::string& string); |
||||
|
||||
void DumpObject(const Json::Object& object); |
||||
void DumpArray(const Json::Array& array); |
||||
void DumpValue(const Json& value); |
||||
|
||||
int indent_; |
||||
int depth_ = 0; |
||||
bool container_empty_ = true; |
||||
bool got_key_ = false; |
||||
std::string output_; |
||||
}; |
||||
|
||||
/* This function checks if there's enough space left in the output buffer,
|
||||
* and will enlarge it if necessary. We're only allocating chunks of 256 |
||||
* bytes at a time (or multiples thereof). |
||||
*/ |
||||
void JsonWriter::OutputCheck(size_t needed) { |
||||
size_t free_space = output_.capacity() - output_.size(); |
||||
if (free_space >= needed) return; |
||||
needed -= free_space; |
||||
/* Round up by 256 bytes. */ |
||||
needed = (needed + 0xff) & ~0xffU; |
||||
output_.reserve(output_.capacity() + needed); |
||||
} |
||||
|
||||
void JsonWriter::OutputChar(char c) { |
||||
OutputCheck(1); |
||||
output_.push_back(c); |
||||
} |
||||
|
||||
void JsonWriter::OutputString(const StringView str) { |
||||
OutputCheck(str.size()); |
||||
output_.append(str.data(), str.size()); |
||||
} |
||||
|
||||
void JsonWriter::OutputIndent() { |
||||
static const char spacesstr[] = |
||||
" " |
||||
" " |
||||
" " |
||||
" "; |
||||
unsigned spaces = static_cast<unsigned>(depth_ * indent_); |
||||
if (indent_ == 0) return; |
||||
if (got_key_) { |
||||
OutputChar(' '); |
||||
return; |
||||
} |
||||
while (spaces >= (sizeof(spacesstr) - 1)) { |
||||
OutputString(StringView(spacesstr, sizeof(spacesstr) - 1)); |
||||
spaces -= static_cast<unsigned>(sizeof(spacesstr) - 1); |
||||
} |
||||
if (spaces == 0) return; |
||||
OutputString(StringView(spacesstr + sizeof(spacesstr) - 1 - spaces, spaces)); |
||||
} |
||||
|
||||
void JsonWriter::ValueEnd() { |
||||
if (container_empty_) { |
||||
container_empty_ = false; |
||||
if (indent_ == 0 || depth_ == 0) return; |
||||
OutputChar('\n'); |
||||
} else { |
||||
OutputChar(','); |
||||
if (indent_ == 0) return; |
||||
OutputChar('\n'); |
||||
} |
||||
} |
||||
|
||||
void JsonWriter::EscapeUtf16(uint16_t utf16) { |
||||
static const char hex[] = "0123456789abcdef"; |
||||
OutputString(StringView("\\u", 2)); |
||||
OutputChar(hex[(utf16 >> 12) & 0x0f]); |
||||
OutputChar(hex[(utf16 >> 8) & 0x0f]); |
||||
OutputChar(hex[(utf16 >> 4) & 0x0f]); |
||||
OutputChar(hex[(utf16)&0x0f]); |
||||
} |
||||
|
||||
void JsonWriter::EscapeString(const std::string& string) { |
||||
OutputChar('"'); |
||||
for (size_t idx = 0; idx < string.size(); ++idx) { |
||||
uint8_t c = static_cast<uint8_t>(string[idx]); |
||||
if (c == 0) { |
||||
break; |
||||
} else if (c >= 32 && c <= 126) { |
||||
if (c == '\\' || c == '"') OutputChar('\\'); |
||||
OutputChar(static_cast<char>(c)); |
||||
} else if (c < 32 || c == 127) { |
||||
switch (c) { |
||||
case '\b': |
||||
OutputString(StringView("\\b", 2)); |
||||
break; |
||||
case '\f': |
||||
OutputString(StringView("\\f", 2)); |
||||
break; |
||||
case '\n': |
||||
OutputString(StringView("\\n", 2)); |
||||
break; |
||||
case '\r': |
||||
OutputString(StringView("\\r", 2)); |
||||
break; |
||||
case '\t': |
||||
OutputString(StringView("\\t", 2)); |
||||
break; |
||||
default: |
||||
EscapeUtf16(c); |
||||
break; |
||||
} |
||||
} else { |
||||
uint32_t utf32 = 0; |
||||
int extra = 0; |
||||
int i; |
||||
int valid = 1; |
||||
if ((c & 0xe0) == 0xc0) { |
||||
utf32 = c & 0x1f; |
||||
extra = 1; |
||||
} else if ((c & 0xf0) == 0xe0) { |
||||
utf32 = c & 0x0f; |
||||
extra = 2; |
||||
} else if ((c & 0xf8) == 0xf0) { |
||||
utf32 = c & 0x07; |
||||
extra = 3; |
||||
} else { |
||||
break; |
||||
} |
||||
for (i = 0; i < extra; i++) { |
||||
utf32 <<= 6; |
||||
++idx; |
||||
/* Breaks out and bail if we hit the end of the string. */ |
||||
if (idx == string.size()) { |
||||
valid = 0; |
||||
break; |
||||
} |
||||
c = static_cast<uint8_t>(string[idx]); |
||||
/* Breaks out and bail on any invalid UTF-8 sequence, including \0. */ |
||||
if ((c & 0xc0) != 0x80) { |
||||
valid = 0; |
||||
break; |
||||
} |
||||
utf32 |= c & 0x3f; |
||||
} |
||||
if (!valid) break; |
||||
/* The range 0xd800 - 0xdfff is reserved by the surrogates ad vitam.
|
||||
* Any other range is technically reserved for future usage, so if we |
||||
* don't want the software to break in the future, we have to allow |
||||
* anything else. The first non-unicode character is 0x110000. */ |
||||
if (((utf32 >= 0xd800) && (utf32 <= 0xdfff)) || (utf32 >= 0x110000)) |
||||
break; |
||||
if (utf32 >= 0x10000) { |
||||
/* If utf32 contains a character that is above 0xffff, it needs to be
|
||||
* broken down into a utf-16 surrogate pair. A surrogate pair is first |
||||
* a high surrogate, followed by a low surrogate. Each surrogate holds |
||||
* 10 bits of usable data, thus allowing a total of 20 bits of data. |
||||
* The high surrogate marker is 0xd800, while the low surrogate marker |
||||
* is 0xdc00. The low 10 bits of each will be the usable data. |
||||
* |
||||
* After re-combining the 20 bits of data, one has to add 0x10000 to |
||||
* the resulting value, in order to obtain the original character. |
||||
* This is obviously because the range 0x0000 - 0xffff can be written |
||||
* without any special trick. |
||||
* |
||||
* Since 0x10ffff is the highest allowed character, we're working in |
||||
* the range 0x00000 - 0xfffff after we decrement it by 0x10000. |
||||
* That range is exactly 20 bits. |
||||
*/ |
||||
utf32 -= 0x10000; |
||||
EscapeUtf16(static_cast<uint16_t>(0xd800 | (utf32 >> 10))); |
||||
EscapeUtf16(static_cast<uint16_t>(0xdc00 | (utf32 & 0x3ff))); |
||||
} else { |
||||
EscapeUtf16(static_cast<uint16_t>(utf32)); |
||||
} |
||||
} |
||||
} |
||||
OutputChar('"'); |
||||
} |
||||
|
||||
void JsonWriter::ContainerBegins(Json::Type type) { |
||||
if (!got_key_) ValueEnd(); |
||||
OutputIndent(); |
||||
OutputChar(type == Json::Type::OBJECT ? '{' : '['); |
||||
container_empty_ = true; |
||||
got_key_ = false; |
||||
depth_++; |
||||
} |
||||
|
||||
void JsonWriter::ContainerEnds(Json::Type type) { |
||||
if (indent_ && !container_empty_) OutputChar('\n'); |
||||
depth_--; |
||||
if (!container_empty_) OutputIndent(); |
||||
OutputChar(type == Json::Type::OBJECT ? '}' : ']'); |
||||
container_empty_ = false; |
||||
got_key_ = false; |
||||
} |
||||
|
||||
void JsonWriter::ObjectKey(const std::string& string) { |
||||
ValueEnd(); |
||||
OutputIndent(); |
||||
EscapeString(string); |
||||
OutputChar(':'); |
||||
got_key_ = true; |
||||
} |
||||
|
||||
void JsonWriter::ValueRaw(const std::string& string) { |
||||
if (!got_key_) ValueEnd(); |
||||
OutputIndent(); |
||||
OutputString(string); |
||||
got_key_ = false; |
||||
} |
||||
|
||||
void JsonWriter::ValueString(const std::string& string) { |
||||
if (!got_key_) ValueEnd(); |
||||
OutputIndent(); |
||||
EscapeString(string); |
||||
got_key_ = false; |
||||
} |
||||
|
||||
void JsonWriter::DumpObject(const Json::Object& object) { |
||||
ContainerBegins(Json::Type::OBJECT); |
||||
for (const auto& p : object) { |
||||
ObjectKey(p.first.data()); |
||||
DumpValue(p.second); |
||||
} |
||||
ContainerEnds(Json::Type::OBJECT); |
||||
} |
||||
|
||||
void JsonWriter::DumpArray(const Json::Array& array) { |
||||
ContainerBegins(Json::Type::ARRAY); |
||||
for (const auto& v : array) { |
||||
DumpValue(v); |
||||
} |
||||
ContainerEnds(Json::Type::ARRAY); |
||||
} |
||||
|
||||
void JsonWriter::DumpValue(const Json& value) { |
||||
switch (value.type()) { |
||||
case Json::Type::OBJECT: |
||||
DumpObject(value.object_value()); |
||||
break; |
||||
case Json::Type::ARRAY: |
||||
DumpArray(value.array_value()); |
||||
break; |
||||
case Json::Type::STRING: |
||||
ValueString(value.string_value()); |
||||
break; |
||||
case Json::Type::NUMBER: |
||||
ValueRaw(value.string_value()); |
||||
break; |
||||
case Json::Type::JSON_TRUE: |
||||
ValueRaw(std::string("true", 4)); |
||||
break; |
||||
case Json::Type::JSON_FALSE: |
||||
ValueRaw(std::string("false", 5)); |
||||
break; |
||||
case Json::Type::JSON_NULL: |
||||
ValueRaw(std::string("null", 4)); |
||||
break; |
||||
default: |
||||
GPR_UNREACHABLE_CODE(abort()); |
||||
} |
||||
} |
||||
|
||||
std::string JsonWriter::Dump(const Json& value, int indent) { |
||||
JsonWriter writer(indent); |
||||
writer.DumpValue(value); |
||||
return std::move(writer.output_); |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
std::string Json::Dump(int indent) const { |
||||
return JsonWriter::Dump(*this, indent); |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -1,106 +0,0 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2015 gRPC authors. |
||||
* |
||||
* 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 <functional> |
||||
|
||||
#include <grpcpp/generic/generic_stub.h> |
||||
#include <grpcpp/impl/rpc_method.h> |
||||
#include <grpcpp/support/client_callback.h> |
||||
|
||||
namespace grpc_impl { |
||||
|
||||
namespace { |
||||
std::unique_ptr<grpc::GenericClientAsyncReaderWriter> CallInternal( |
||||
grpc::ChannelInterface* channel, grpc::ClientContext* context, |
||||
const grpc::string& method, CompletionQueue* cq, bool start, void* tag) { |
||||
return std::unique_ptr<grpc::GenericClientAsyncReaderWriter>( |
||||
internal::ClientAsyncReaderWriterFactory<grpc::ByteBuffer, |
||||
grpc::ByteBuffer>:: |
||||
Create(channel, cq, |
||||
grpc::internal::RpcMethod( |
||||
method.c_str(), grpc::internal::RpcMethod::BIDI_STREAMING), |
||||
context, start, tag)); |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
// begin a call to a named method
|
||||
std::unique_ptr<grpc::GenericClientAsyncReaderWriter> GenericStub::Call( |
||||
grpc::ClientContext* context, const grpc::string& method, |
||||
CompletionQueue* cq, void* tag) { |
||||
return CallInternal(channel_.get(), context, method, cq, true, tag); |
||||
} |
||||
|
||||
// setup a call to a named method
|
||||
std::unique_ptr<grpc::GenericClientAsyncReaderWriter> GenericStub::PrepareCall( |
||||
grpc::ClientContext* context, const grpc::string& method, |
||||
CompletionQueue* cq) { |
||||
return CallInternal(channel_.get(), context, method, cq, false, nullptr); |
||||
} |
||||
|
||||
// setup a unary call to a named method
|
||||
std::unique_ptr<grpc::GenericClientAsyncResponseReader> |
||||
GenericStub::PrepareUnaryCall(grpc::ClientContext* context, |
||||
const grpc::string& method, |
||||
const grpc::ByteBuffer& request, |
||||
CompletionQueue* cq) { |
||||
return std::unique_ptr<grpc::GenericClientAsyncResponseReader>( |
||||
internal::ClientAsyncResponseReaderFactory<grpc::ByteBuffer>::Create( |
||||
channel_.get(), cq, |
||||
grpc::internal::RpcMethod(method.c_str(), |
||||
grpc::internal::RpcMethod::NORMAL_RPC), |
||||
context, request, false)); |
||||
} |
||||
|
||||
void GenericStub::UnaryCallInternal( |
||||
grpc::ClientContext* context, const grpc::string& method, |
||||
const grpc::ByteBuffer* request, grpc::ByteBuffer* response, |
||||
std::function<void(grpc::Status)> on_completion) { |
||||
internal::CallbackUnaryCall( |
||||
channel_.get(), |
||||
grpc::internal::RpcMethod(method.c_str(), |
||||
grpc::internal::RpcMethod::NORMAL_RPC), |
||||
context, request, response, std::move(on_completion)); |
||||
} |
||||
|
||||
void GenericStub::PrepareBidiStreamingCallInternal( |
||||
grpc::ClientContext* context, const grpc::string& method, |
||||
ClientBidiReactor<grpc::ByteBuffer, grpc::ByteBuffer>* reactor) { |
||||
internal::ClientCallbackReaderWriterFactory< |
||||
grpc::ByteBuffer, |
||||
grpc::ByteBuffer>::Create(channel_.get(), |
||||
grpc::internal::RpcMethod( |
||||
method.c_str(), |
||||
grpc::internal::RpcMethod::BIDI_STREAMING), |
||||
context, reactor); |
||||
} |
||||
|
||||
void GenericStub::PrepareUnaryCallInternal(grpc::ClientContext* context, |
||||
const grpc::string& method, |
||||
const grpc::ByteBuffer* request, |
||||
grpc::ByteBuffer* response, |
||||
ClientUnaryReactor* reactor) { |
||||
internal::ClientCallbackUnaryFactory::Create<grpc::ByteBuffer, |
||||
grpc::ByteBuffer>( |
||||
channel_.get(), |
||||
grpc::internal::RpcMethod(method.c_str(), |
||||
grpc::internal::RpcMethod::NORMAL_RPC), |
||||
context, request, response, reactor); |
||||
} |
||||
|
||||
} // namespace grpc_impl
|
@ -0,0 +1,67 @@ |
||||
# Copyright 2020 The gRPC Authors |
||||
# |
||||
# 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. |
||||
"""Testing the channel_ready function.""" |
||||
|
||||
import asyncio |
||||
import gc |
||||
import logging |
||||
import time |
||||
import unittest |
||||
|
||||
import grpc |
||||
from grpc.experimental import aio |
||||
|
||||
from tests.unit.framework.common import get_socket, test_constants |
||||
from tests_aio.unit import _common |
||||
from tests_aio.unit._test_base import AioTestBase |
||||
from tests_aio.unit._test_server import start_test_server |
||||
|
||||
|
||||
class TestChannelReady(AioTestBase): |
||||
|
||||
async def setUp(self): |
||||
address, self._port, self._socket = get_socket(listen=False) |
||||
self._channel = aio.insecure_channel(f"{address}:{self._port}") |
||||
self._socket.close() |
||||
|
||||
async def tearDown(self): |
||||
await self._channel.close() |
||||
|
||||
async def test_channel_ready_success(self): |
||||
# Start `channel_ready` as another Task |
||||
channel_ready_task = self.loop.create_task( |
||||
self._channel.channel_ready()) |
||||
|
||||
# Wait for TRANSIENT_FAILURE |
||||
await _common.block_until_certain_state( |
||||
self._channel, grpc.ChannelConnectivity.TRANSIENT_FAILURE) |
||||
|
||||
try: |
||||
# Start the server |
||||
_, server = await start_test_server(port=self._port) |
||||
|
||||
# The RPC should recover itself |
||||
await channel_ready_task |
||||
finally: |
||||
await server.stop(None) |
||||
|
||||
async def test_channel_ready_blocked(self): |
||||
with self.assertRaises(asyncio.TimeoutError): |
||||
await asyncio.wait_for(self._channel.channel_ready(), |
||||
test_constants.SHORT_TIMEOUT) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
logging.basicConfig(level=logging.DEBUG) |
||||
unittest.main(verbosity=2) |
@ -0,0 +1,196 @@ |
||||
# Copyright 2020 The gRPC Authors |
||||
# |
||||
# 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. |
||||
"""Tests behavior around the compression mechanism.""" |
||||
|
||||
import asyncio |
||||
import logging |
||||
import platform |
||||
import random |
||||
import unittest |
||||
|
||||
import grpc |
||||
from grpc.experimental import aio |
||||
|
||||
from tests_aio.unit._test_base import AioTestBase |
||||
from tests_aio.unit import _common |
||||
|
||||
_GZIP_CHANNEL_ARGUMENT = ('grpc.default_compression_algorithm', 2) |
||||
_GZIP_DISABLED_CHANNEL_ARGUMENT = ('grpc.compression_enabled_algorithms_bitset', |
||||
3) |
||||
_DEFLATE_DISABLED_CHANNEL_ARGUMENT = ( |
||||
'grpc.compression_enabled_algorithms_bitset', 5) |
||||
|
||||
_TEST_UNARY_UNARY = '/test/TestUnaryUnary' |
||||
_TEST_SET_COMPRESSION = '/test/TestSetCompression' |
||||
_TEST_DISABLE_COMPRESSION_UNARY = '/test/TestDisableCompressionUnary' |
||||
_TEST_DISABLE_COMPRESSION_STREAM = '/test/TestDisableCompressionStream' |
||||
|
||||
_REQUEST = b'\x01' * 100 |
||||
_RESPONSE = b'\x02' * 100 |
||||
|
||||
|
||||
async def _test_unary_unary(unused_request, unused_context): |
||||
return _RESPONSE |
||||
|
||||
|
||||
async def _test_set_compression(unused_request_iterator, context): |
||||
assert _REQUEST == await context.read() |
||||
context.set_compression(grpc.Compression.Deflate) |
||||
await context.write(_RESPONSE) |
||||
try: |
||||
context.set_compression(grpc.Compression.Deflate) |
||||
except RuntimeError: |
||||
# NOTE(lidiz) Testing if the servicer context raises exception when |
||||
# the set_compression method is called after initial_metadata sent. |
||||
# After the initial_metadata sent, the server-side has no control over |
||||
# which compression algorithm it should use. |
||||
pass |
||||
else: |
||||
raise ValueError( |
||||
'Expecting exceptions if set_compression is not effective') |
||||
|
||||
|
||||
async def _test_disable_compression_unary(request, context): |
||||
assert _REQUEST == request |
||||
context.set_compression(grpc.Compression.Deflate) |
||||
context.disable_next_message_compression() |
||||
return _RESPONSE |
||||
|
||||
|
||||
async def _test_disable_compression_stream(unused_request_iterator, context): |
||||
assert _REQUEST == await context.read() |
||||
context.set_compression(grpc.Compression.Deflate) |
||||
await context.write(_RESPONSE) |
||||
context.disable_next_message_compression() |
||||
await context.write(_RESPONSE) |
||||
await context.write(_RESPONSE) |
||||
|
||||
|
||||
_ROUTING_TABLE = { |
||||
_TEST_UNARY_UNARY: |
||||
grpc.unary_unary_rpc_method_handler(_test_unary_unary), |
||||
_TEST_SET_COMPRESSION: |
||||
grpc.stream_stream_rpc_method_handler(_test_set_compression), |
||||
_TEST_DISABLE_COMPRESSION_UNARY: |
||||
grpc.unary_unary_rpc_method_handler(_test_disable_compression_unary), |
||||
_TEST_DISABLE_COMPRESSION_STREAM: |
||||
grpc.stream_stream_rpc_method_handler(_test_disable_compression_stream), |
||||
} |
||||
|
||||
|
||||
class _GenericHandler(grpc.GenericRpcHandler): |
||||
|
||||
def service(self, handler_call_details): |
||||
return _ROUTING_TABLE.get(handler_call_details.method) |
||||
|
||||
|
||||
async def _start_test_server(options=None): |
||||
server = aio.server(options=options) |
||||
port = server.add_insecure_port('[::]:0') |
||||
server.add_generic_rpc_handlers((_GenericHandler(),)) |
||||
await server.start() |
||||
return f'localhost:{port}', server |
||||
|
||||
|
||||
class TestCompression(AioTestBase): |
||||
|
||||
async def setUp(self): |
||||
server_options = (_GZIP_DISABLED_CHANNEL_ARGUMENT,) |
||||
self._address, self._server = await _start_test_server(server_options) |
||||
self._channel = aio.insecure_channel(self._address) |
||||
|
||||
async def tearDown(self): |
||||
await self._channel.close() |
||||
await self._server.stop(None) |
||||
|
||||
async def test_channel_level_compression_baned_compression(self): |
||||
# GZIP is disabled, this call should fail |
||||
async with aio.insecure_channel( |
||||
self._address, compression=grpc.Compression.Gzip) as channel: |
||||
multicallable = channel.unary_unary(_TEST_UNARY_UNARY) |
||||
call = multicallable(_REQUEST) |
||||
with self.assertRaises(aio.AioRpcError) as exception_context: |
||||
await call |
||||
rpc_error = exception_context.exception |
||||
self.assertEqual(grpc.StatusCode.UNIMPLEMENTED, rpc_error.code()) |
||||
|
||||
async def test_channel_level_compression_allowed_compression(self): |
||||
# Deflate is allowed, this call should succeed |
||||
async with aio.insecure_channel( |
||||
self._address, compression=grpc.Compression.Deflate) as channel: |
||||
multicallable = channel.unary_unary(_TEST_UNARY_UNARY) |
||||
call = multicallable(_REQUEST) |
||||
self.assertEqual(grpc.StatusCode.OK, await call.code()) |
||||
|
||||
async def test_client_call_level_compression_baned_compression(self): |
||||
multicallable = self._channel.unary_unary(_TEST_UNARY_UNARY) |
||||
|
||||
# GZIP is disabled, this call should fail |
||||
call = multicallable(_REQUEST, compression=grpc.Compression.Gzip) |
||||
with self.assertRaises(aio.AioRpcError) as exception_context: |
||||
await call |
||||
rpc_error = exception_context.exception |
||||
self.assertEqual(grpc.StatusCode.UNIMPLEMENTED, rpc_error.code()) |
||||
|
||||
async def test_client_call_level_compression_allowed_compression(self): |
||||
multicallable = self._channel.unary_unary(_TEST_UNARY_UNARY) |
||||
|
||||
# Deflate is allowed, this call should succeed |
||||
call = multicallable(_REQUEST, compression=grpc.Compression.Deflate) |
||||
self.assertEqual(grpc.StatusCode.OK, await call.code()) |
||||
|
||||
async def test_server_call_level_compression(self): |
||||
multicallable = self._channel.stream_stream(_TEST_SET_COMPRESSION) |
||||
call = multicallable() |
||||
await call.write(_REQUEST) |
||||
await call.done_writing() |
||||
self.assertEqual(_RESPONSE, await call.read()) |
||||
self.assertEqual(grpc.StatusCode.OK, await call.code()) |
||||
|
||||
async def test_server_disable_compression_unary(self): |
||||
multicallable = self._channel.unary_unary( |
||||
_TEST_DISABLE_COMPRESSION_UNARY) |
||||
call = multicallable(_REQUEST) |
||||
self.assertEqual(_RESPONSE, await call) |
||||
self.assertEqual(grpc.StatusCode.OK, await call.code()) |
||||
|
||||
async def test_server_disable_compression_stream(self): |
||||
multicallable = self._channel.stream_stream( |
||||
_TEST_DISABLE_COMPRESSION_STREAM) |
||||
call = multicallable() |
||||
await call.write(_REQUEST) |
||||
await call.done_writing() |
||||
self.assertEqual(_RESPONSE, await call.read()) |
||||
self.assertEqual(_RESPONSE, await call.read()) |
||||
self.assertEqual(_RESPONSE, await call.read()) |
||||
self.assertEqual(grpc.StatusCode.OK, await call.code()) |
||||
|
||||
async def test_server_default_compression_algorithm(self): |
||||
server = aio.server(compression=grpc.Compression.Deflate) |
||||
port = server.add_insecure_port('[::]:0') |
||||
server.add_generic_rpc_handlers((_GenericHandler(),)) |
||||
await server.start() |
||||
|
||||
async with aio.insecure_channel(f'localhost:{port}') as channel: |
||||
multicallable = channel.unary_unary(_TEST_UNARY_UNARY) |
||||
call = multicallable(_REQUEST) |
||||
self.assertEqual(_RESPONSE, await call) |
||||
self.assertEqual(grpc.StatusCode.OK, await call.code()) |
||||
|
||||
await server.stop(None) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
logging.basicConfig(level=logging.DEBUG) |
||||
unittest.main(verbosity=2) |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue