summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVolodymyr Gotra <vgotra@gmail.com>2016-09-14 19:18:48 -0500
committerJens Geyer <jensg@apache.org>2017-01-04 19:40:30 +0100
commitb587a12a116cc394b62d9af2bbcecd50cfb18ce3 (patch)
tree33ab9b38c10c1d14eb5ffcc3167192de00e767e4
parent3c55440230f3645816913d9c53b42dcc16b70f95 (diff)
downloadthrift-b587a12a116cc394b62d9af2bbcecd50cfb18ce3.tar.gz
THRIFT-3933 Microsoft .Net Core library port and generator for this library
Client: .NET Core Patch: Volodymyr Gotra <vgotra@gmail.com> PR #1088, with significant improvements by Jens Geyer <jensg@apache.org> PR #1149 This closes #1088 This closes #1149
-rw-r--r--.gitignore15
-rw-r--r--aclocal/ax_prog_dotnetcore_version.m461
-rw-r--r--compiler/cpp/CMakeLists.txt1
-rw-r--r--compiler/cpp/Makefile.am1
-rw-r--r--compiler/cpp/compiler.vcxproj1
-rw-r--r--compiler/cpp/src/thrift/generate/t_generator.h12
-rw-r--r--compiler/cpp/src/thrift/generate/t_netcore_generator.cc3186
-rwxr-xr-xconfigure.ac21
-rw-r--r--contrib/fb303/if/fb303.thrift1
-rw-r--r--lib/Makefile.am4
-rw-r--r--lib/netcore/Makefile.am108
-rw-r--r--lib/netcore/README.md21
-rw-r--r--lib/netcore/Tests/Thrift.PublicInterfaces.Compile.Tests/CassandraTest.thrift705
-rw-r--r--lib/netcore/Tests/Thrift.PublicInterfaces.Compile.Tests/Properties/AssemblyInfo.cs40
-rw-r--r--lib/netcore/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.xproj21
-rw-r--r--lib/netcore/Tests/Thrift.PublicInterfaces.Compile.Tests/project.json25
-rw-r--r--lib/netcore/Thrift.sln38
-rw-r--r--lib/netcore/Thrift/Collections/TCollections.cs101
-rw-r--r--lib/netcore/Thrift/Collections/THashSet.cs67
-rw-r--r--lib/netcore/Thrift/ITAsyncProcessor.cs29
-rw-r--r--lib/netcore/Thrift/ITProcessorFactory.cs28
-rw-r--r--lib/netcore/Thrift/Properties/AssemblyInfo.cs56
-rw-r--r--lib/netcore/Thrift/Protocols/Entities/TField.cs37
-rw-r--r--lib/netcore/Thrift/Protocols/Entities/TList.cs33
-rw-r--r--lib/netcore/Thrift/Protocols/Entities/TMap.cs36
-rw-r--r--lib/netcore/Thrift/Protocols/Entities/TMessage.cs37
-rw-r--r--lib/netcore/Thrift/Protocols/Entities/TMessageType.cs28
-rw-r--r--lib/netcore/Thrift/Protocols/Entities/TSet.cs38
-rw-r--r--lib/netcore/Thrift/Protocols/Entities/TStruct.cs30
-rw-r--r--lib/netcore/Thrift/Protocols/Entities/TType.cs37
-rw-r--r--lib/netcore/Thrift/Protocols/ITProtocolFactory.cs27
-rw-r--r--lib/netcore/Thrift/Protocols/TAbstractBase.cs28
-rw-r--r--lib/netcore/Thrift/Protocols/TBase.cs28
-rw-r--r--lib/netcore/Thrift/Protocols/TBinaryProtocol.cs608
-rw-r--r--lib/netcore/Thrift/Protocols/TCompactProtocol.cs922
-rw-r--r--lib/netcore/Thrift/Protocols/TJSONProtocol.cs1170
-rw-r--r--lib/netcore/Thrift/Protocols/TMultiplexedProtocol.cs100
-rw-r--r--lib/netcore/Thrift/Protocols/TProtocol.cs377
-rw-r--r--lib/netcore/Thrift/Protocols/TProtocolDecorator.cs252
-rw-r--r--lib/netcore/Thrift/Protocols/TProtocolException.cs58
-rw-r--r--lib/netcore/Thrift/Protocols/Utilities/TBase64Utils.cs101
-rw-r--r--lib/netcore/Thrift/Protocols/Utilities/TProtocolUtil.cs108
-rw-r--r--lib/netcore/Thrift/Server/AsyncBaseServer.cs184
-rw-r--r--lib/netcore/Thrift/Server/TBaseServer.cs86
-rw-r--r--lib/netcore/Thrift/Server/TServerEventHandler.cs54
-rw-r--r--lib/netcore/Thrift/SingletonTProcessorFactory.cs38
-rw-r--r--lib/netcore/Thrift/TApplicationException.cs149
-rw-r--r--lib/netcore/Thrift/TBaseClient.cs98
-rw-r--r--lib/netcore/Thrift/TException.cs34
-rw-r--r--lib/netcore/Thrift/TMultiplexedProcessor.cs143
-rw-r--r--lib/netcore/Thrift/Thrift.xproj21
-rw-r--r--lib/netcore/Thrift/Transports/Client/TBufferedClientTransport.cs210
-rw-r--r--lib/netcore/Thrift/Transports/Client/TFramedClientTransport.cs207
-rw-r--r--lib/netcore/Thrift/Transports/Client/THttpClientTransport.cs228
-rw-r--r--lib/netcore/Thrift/Transports/Client/TMemoryBufferClientTransport.cs97
-rw-r--r--lib/netcore/Thrift/Transports/Client/TNamedPipeClientTransport.cs95
-rw-r--r--lib/netcore/Thrift/Transports/Client/TSocketClientTransport.cs144
-rw-r--r--lib/netcore/Thrift/Transports/Client/TStreamClientTransport.cs110
-rw-r--r--lib/netcore/Thrift/Transports/Client/TTlsSocketClientTransport.cs236
-rw-r--r--lib/netcore/Thrift/Transports/Server/THttpServerTransport.cs113
-rw-r--r--lib/netcore/Thrift/Transports/Server/TNamedPipeServerTransport.cs191
-rw-r--r--lib/netcore/Thrift/Transports/Server/TServerSocketTransport.cs162
-rw-r--r--lib/netcore/Thrift/Transports/Server/TTlsServerSocketTransport.cs156
-rw-r--r--lib/netcore/Thrift/Transports/TClientTransport.cs178
-rw-r--r--lib/netcore/Thrift/Transports/TServerTransport.cs54
-rw-r--r--lib/netcore/Thrift/Transports/TTransportException.cs58
-rw-r--r--lib/netcore/Thrift/Transports/TTransportFactory.cs35
-rw-r--r--lib/netcore/Thrift/project.json19
-rw-r--r--lib/netcore/build.cmd36
-rwxr-xr-xlib/netcore/build.sh48
-rw-r--r--lib/netcore/global.json3
-rwxr-xr-xtest/Makefile.am4
-rw-r--r--test/ThriftTest.thrift1
-rw-r--r--test/netcore/Makefile.am68
-rw-r--r--test/netcore/ThriftTest/Program.cs76
-rw-r--r--test/netcore/ThriftTest/Properties/AssemblyInfo.cs43
-rw-r--r--test/netcore/ThriftTest/Properties/launchSettings.json7
-rw-r--r--test/netcore/ThriftTest/TestClient.cs893
-rw-r--r--test/netcore/ThriftTest/TestServer.cs556
-rw-r--r--test/netcore/ThriftTest/ThriftTest.sln33
-rw-r--r--test/netcore/ThriftTest/ThriftTest.xproj21
-rw-r--r--test/netcore/ThriftTest/project.json29
-rw-r--r--test/netcore/build.cmd45
-rw-r--r--test/netcore/build.sh54
-rw-r--r--test/netcore/global.json3
-rw-r--r--test/tests.json28
-rwxr-xr-xtutorial/Makefile.am4
-rw-r--r--tutorial/netcore/.gitignore1
-rw-r--r--tutorial/netcore/Client/Client.xproj21
-rw-r--r--tutorial/netcore/Client/Program.cs277
-rw-r--r--tutorial/netcore/Client/Properties/AssemblyInfo.cs40
-rw-r--r--tutorial/netcore/Client/Properties/launchSettings.json8
-rw-r--r--tutorial/netcore/Client/ThriftTest.pfxbin0 -> 2661 bytes
-rw-r--r--tutorial/netcore/Client/project.json28
-rw-r--r--tutorial/netcore/Interfaces/Interfaces.xproj21
-rw-r--r--tutorial/netcore/Interfaces/Properties/AssemblyInfo.cs40
-rw-r--r--tutorial/netcore/Interfaces/project.json22
-rw-r--r--tutorial/netcore/Makefile.am82
-rw-r--r--tutorial/netcore/README.md253
-rw-r--r--tutorial/netcore/Server/Program.cs397
-rw-r--r--tutorial/netcore/Server/Properties/AssemblyInfo.cs40
-rw-r--r--tutorial/netcore/Server/Properties/launchSettings.json8
-rw-r--r--tutorial/netcore/Server/Server.xproj21
-rw-r--r--tutorial/netcore/Server/ThriftTest.pfxbin0 -> 2661 bytes
-rw-r--r--tutorial/netcore/Server/project.json29
-rw-r--r--tutorial/netcore/Tutorial.sln45
-rw-r--r--tutorial/netcore/build.cmd45
-rw-r--r--tutorial/netcore/build.sh44
-rw-r--r--tutorial/netcore/global.json3
-rw-r--r--tutorial/shared.thrift1
-rw-r--r--tutorial/tutorial.thrift1
111 files changed, 15076 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 140c93b00..9d2463efe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,6 +36,7 @@ node_modules
compile
test-driver
erl_crash.dump
+project.lock.json
.sonar
.DS_Store
@@ -215,6 +216,10 @@ erl_crash.dump
/lib/hs/dist
/lib/java/build
/lib/js/test/build
+/lib/netcore/**/.vs
+/lib/netcore/**/bin
+/lib/netcore/**/obj
+/lib/netcore/**/gen-*
/lib/nodejs/coverage
/lib/nodejs/node_modules/
/lib/perl/MANIFEST
@@ -290,6 +295,11 @@ erl_crash.dump
/test/hs/TestServer
/test/py.twisted/_trial_temp/
/test/rb/Gemfile.lock
+/test/netcore/**/.vs
+/test/netcore/**/bin
+/test/netcore/**/obj
+/test/netcore/**/gen-*
+/test/netcore/Thrift
/tutorial/cpp/TutorialClient
/tutorial/cpp/TutorialServer
/tutorial/c_glib/tutorial_client
@@ -323,4 +333,9 @@ erl_crash.dump
/tutorial/hs/dist/
/tutorial/java/build/
/tutorial/js/build/
+/tutorial/netcore/**/.vs
+/tutorial/netcore/**/bin
+/tutorial/netcore/**/obj
+/tutorial/netcore/**/gen-*
+/tutorial/netcore/Thrift
/ylwrap
diff --git a/aclocal/ax_prog_dotnetcore_version.m4 b/aclocal/ax_prog_dotnetcore_version.m4
new file mode 100644
index 000000000..45c7a4e1a
--- /dev/null
+++ b/aclocal/ax_prog_dotnetcore_version.m4
@@ -0,0 +1,61 @@
+# ==============================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_prog_dotnetcore_version.html
+# ==============================================================================
+#
+# SYNOPSIS
+#
+# AX_PROG_DOTNETCORE_VERSION([VERSION],[ACTION-IF-TRUE],[ACTION-IF-FALSE])
+#
+# DESCRIPTION
+#
+# Makes sure that .NET Core supports the version indicated. If true the
+# shell commands in ACTION-IF-TRUE are executed. If not the shell commands
+# in ACTION-IF-FALSE are run. The $dotnetcore_version variable will be
+# filled with the detected version.
+#
+# This macro uses the $DOTNETCORE variable to perform the check. If
+# $DOTNETCORE is not set prior to calling this macro, the macro will fail.
+#
+# Example:
+#
+# AC_PATH_PROG([DOTNETCORE],[dotnet])
+# AC_PROG_DOTNETCORE_VERSION([1.0.2],[ ... ],[ ... ])
+#
+# Searches for .NET Core, then checks if at least version 1.0.2 is
+# present.
+#
+# LICENSE
+#
+# Copyright (c) 2016 Jens Geyer <jensg@apache.org>
+#
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved. This file is offered as-is, without any
+# warranty.
+
+#serial 1
+
+AC_DEFUN([AX_PROG_DOTNETCORE_VERSION],[
+ AC_REQUIRE([AC_PROG_SED])
+
+ AS_IF([test -n "$DOTNETCORE"],[
+ ax_dotnetcore_version="$1"
+
+ AC_MSG_CHECKING([for .NET Core version])
+ dotnetcore_version=`$DOTNETCORE --version 2>&1 | $SED -e 's/\(@<:@0-9@:>@*\.@<:@0-9@:>@*\.@<:@0-9@:>@*\)\(.*\)/\1/'`
+ AC_MSG_RESULT($dotnetcore_version)
+
+ AC_SUBST([DOTNETCORE_VERSION],[$dotnetcore_version])
+
+ AX_COMPARE_VERSION([$ax_dotnetcore_version],[le],[$dotnetcore_version],[
+ :
+ $2
+ ],[
+ :
+ $3
+ ])
+ ],[
+ AC_MSG_WARN([could not find .NET Core])
+ $3
+ ])
+])
diff --git a/compiler/cpp/CMakeLists.txt b/compiler/cpp/CMakeLists.txt
index 02ed78c64..85e351dcc 100644
--- a/compiler/cpp/CMakeLists.txt
+++ b/compiler/cpp/CMakeLists.txt
@@ -74,6 +74,7 @@ THRIFT_ADD_COMPILER(as3 "Enable compiler for ActionScript 3" ON)
THRIFT_ADD_COMPILER(dart "Enable compiler for Dart" ON)
THRIFT_ADD_COMPILER(haxe "Enable compiler for Haxe" ON)
THRIFT_ADD_COMPILER(csharp "Enable compiler for C#" ON)
+THRIFT_ADD_COMPILER(netcore "Enable compiler for .NET Core" ON)
THRIFT_ADD_COMPILER(py "Enable compiler for Python 2.0" ON)
THRIFT_ADD_COMPILER(rb "Enable compiler for Ruby" ON)
THRIFT_ADD_COMPILER(perl "Enable compiler for Perl" ON)
diff --git a/compiler/cpp/Makefile.am b/compiler/cpp/Makefile.am
index a2393916d..5d424b4e0 100644
--- a/compiler/cpp/Makefile.am
+++ b/compiler/cpp/Makefile.am
@@ -87,6 +87,7 @@ thrift_SOURCES += src/thrift/generate/t_c_glib_generator.cc \
src/thrift/generate/t_dart_generator.cc \
src/thrift/generate/t_haxe_generator.cc \
src/thrift/generate/t_csharp_generator.cc \
+ src/thrift/generate/t_netcore_generator.cc \
src/thrift/generate/t_py_generator.cc \
src/thrift/generate/t_rb_generator.cc \
src/thrift/generate/t_perl_generator.cc \
diff --git a/compiler/cpp/compiler.vcxproj b/compiler/cpp/compiler.vcxproj
index 878e2199a..1e8636061 100644
--- a/compiler/cpp/compiler.vcxproj
+++ b/compiler/cpp/compiler.vcxproj
@@ -57,6 +57,7 @@
<ClCompile Include="src\thrift\generate\t_cocoa_generator.cc" />
<ClCompile Include="src\thrift\generate\t_cpp_generator.cc" />
<ClCompile Include="src\thrift\generate\t_csharp_generator.cc" />
+ <ClCompile Include="src\thrift\generate\t_netcore_generator.cc" />
<ClCompile Include="src\thrift\generate\t_c_glib_generator.cc" />
<ClCompile Include="src\thrift\generate\t_d_generator.cc" />
<ClCompile Include="src\thrift\generate\t_dart_generator.cc" />
diff --git a/compiler/cpp/src/thrift/generate/t_generator.h b/compiler/cpp/src/thrift/generate/t_generator.h
index 051bbc423..fc3f32321 100644
--- a/compiler/cpp/src/thrift/generate/t_generator.h
+++ b/compiler/cpp/src/thrift/generate/t_generator.h
@@ -173,6 +173,18 @@ protected:
void indent_down() { --indent_; }
/**
+ * Indentation validation helper
+ */
+ int indent_count() { return indent_; }
+
+ void indent_validate( int expected, const char * func_name) {
+ if (indent_ != expected) {
+ pverbose("Wrong indent count in %s: difference = %i \n", func_name, (expected - indent_));
+ }
+ }
+
+
+ /**
* Indentation print function
*/
std::string indent() {
diff --git a/compiler/cpp/src/thrift/generate/t_netcore_generator.cc b/compiler/cpp/src/thrift/generate/t_netcore_generator.cc
new file mode 100644
index 000000000..f4298c2f6
--- /dev/null
+++ b/compiler/cpp/src/thrift/generate/t_netcore_generator.cc
@@ -0,0 +1,3186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * Contains some contributions under the Thrift Software License.
+ * Please see doc/old-thrift-license.txt in the Thrift distribution for
+ * details.
+ */
+
+#include <cassert>
+
+#include <string>
+#include <fstream>
+#include <iostream>
+#include <vector>
+#include <cctype>
+
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sstream>
+
+#include "thrift/platform.h"
+#include "thrift/generate/t_oop_generator.h"
+
+using std::map;
+using std::ofstream;
+using std::ostringstream;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+//TODO: check for indentation
+//TODO: Do we need seqId_ in generation?
+
+static const string endl = "\n"; // avoid ostream << std::endl flushes
+
+struct member_mapping_scope
+{
+ void* scope_member;
+ map<string, string> mapping_table;
+};
+
+class t_netcore_generator : public t_oop_generator
+{
+public:
+ t_netcore_generator(t_program* program, const map<string, string>& parsed_options, const string& option_string)
+ : t_oop_generator(program)
+ {
+ (void)option_string;
+
+ nullable_ = false;
+ hashcode_ = false;
+ union_ = false;
+ serialize_ = false;
+ wcf_ = false;
+ wcf_namespace_.clear();
+
+ map<string, string>::const_iterator iter;
+
+ for (iter = parsed_options.begin(); iter != parsed_options.end(); ++iter)
+ {
+ if (iter->first.compare("nullable") == 0)
+ {
+ nullable_ = true;
+ }
+ else if (iter->first.compare("hashcode") == 0)
+ {
+ hashcode_ = true;
+ }
+ else if (iter->first.compare("union") == 0)
+ {
+ union_ = true;
+ }
+ else if (iter->first.compare("serial") == 0)
+ {
+ serialize_ = true;
+ wcf_namespace_ = iter->second; // since there can be only one namespace
+ }
+ else if (iter->first.compare("wcf") == 0)
+ {
+ wcf_ = true;
+ wcf_namespace_ = iter->second;
+ }
+ else
+ {
+ throw "unknown option netcore:" + iter->first;
+ }
+ }
+
+ out_dir_base_ = "gen-netcore";
+ }
+
+ // overrides
+ void init_generator();
+ void close_generator();
+ void generate_consts(vector<t_const*> consts);
+ void generate_typedef(t_typedef* ttypedef);
+ void generate_enum(t_enum* tenum);
+ void generate_struct(t_struct* tstruct);
+ void generate_xception(t_struct* txception);
+ void generate_service(t_service* tservice);
+
+ void generate_property(ofstream& out, t_field* tfield, bool isPublic, bool generateIsset);
+ void generate_netcore_property(ofstream& out, t_field* tfield, bool isPublic, bool includeIsset = true, string fieldPrefix = "");
+ bool print_const_value(ofstream& out, string name, t_type* type, t_const_value* value, bool in_static, bool defval = false, bool needtype = false);
+ string render_const_value(ofstream& out, string name, t_type* type, t_const_value* value);
+ void print_const_constructor(ofstream& out, vector<t_const*> consts);
+ void print_const_def_value(ofstream& out, string name, t_type* type, t_const_value* value);
+ void generate_netcore_struct(t_struct* tstruct, bool is_exception);
+ void generate_netcore_union(t_struct* tunion);
+ void generate_netcore_struct_definition(ofstream& out, t_struct* tstruct, bool is_xception = false, bool in_class = false, bool is_result = false);
+ void generate_netcore_union_definition(ofstream& out, t_struct* tunion);
+ void generate_netcore_union_class(ofstream& out, t_struct* tunion, t_field* tfield);
+ void generate_netcore_wcffault(ofstream& out, t_struct* tstruct);
+ void generate_netcore_struct_reader(ofstream& out, t_struct* tstruct);
+ void generate_netcore_struct_result_writer(ofstream& out, t_struct* tstruct);
+ void generate_netcore_struct_writer(ofstream& out, t_struct* tstruct);
+ void generate_netcore_struct_tostring(ofstream& out, t_struct* tstruct);
+ void generate_netcore_struct_equals(ofstream& out, t_struct* tstruct);
+ void generate_netcore_struct_hashcode(ofstream& out, t_struct* tstruct);
+ void generate_netcore_union_reader(ofstream& out, t_struct* tunion);
+ void generate_function_helpers(ofstream& out, t_function* tfunction);
+ void generate_service_interface(ofstream& out, t_service* tservice);
+ void generate_service_helpers(ofstream& out, t_service* tservice);
+ void generate_service_client(ofstream& out, t_service* tservice);
+ void generate_service_server(ofstream& out, t_service* tservice);
+ void generate_process_function_async(ofstream& out, t_service* tservice, t_function* function);
+ void generate_deserialize_field(ofstream& out, t_field* tfield, string prefix = "", bool is_propertyless = false);
+ void generate_deserialize_struct(ofstream& out, t_struct* tstruct, string prefix = "");
+ void generate_deserialize_container(ofstream& out, t_type* ttype, string prefix = "");
+ void generate_deserialize_set_element(ofstream& out, t_set* tset, string prefix = "");
+ void generate_deserialize_map_element(ofstream& out, t_map* tmap, string prefix = "");
+ void generate_deserialize_list_element(ofstream& out, t_list* list, string prefix = "");
+ void generate_serialize_field(ofstream& out, t_field* tfield, string prefix = "", bool is_element = false, bool is_propertyless = false);
+ void generate_serialize_struct(ofstream& out, t_struct* tstruct, string prefix = "");
+ void generate_serialize_container(ofstream& out, t_type* ttype, string prefix = "");
+ void generate_serialize_map_element(ofstream& out, t_map* tmap, string iter, string map);
+ void generate_serialize_set_element(ofstream& out, t_set* tmap, string iter);
+ void generate_serialize_list_element(ofstream& out, t_list* tlist, string iter);
+ void generate_netcore_doc(ofstream& out, t_field* field);
+ void generate_netcore_doc(ofstream& out, t_doc* tdoc);
+ void generate_netcore_doc(ofstream& out, t_function* tdoc);
+ void generate_netcore_docstring_comment(ofstream& out, string contents);
+ void docstring_comment(ofstream& out, const string& comment_start, const string& line_prefix, const string& contents, const string& comment_end);
+ void start_netcore_namespace(ofstream& out);
+ void end_netcore_namespace(ofstream& out);
+
+ string netcore_type_usings() const;
+ string netcore_thrift_usings() const;
+ string type_name(t_type* ttype, bool in_countainer = false, bool in_init = false, bool in_param = false, bool is_required = false);
+ string base_type_name(t_base_type* tbase, bool in_container = false, bool in_param = false, bool is_required = false);
+ string declare_field(t_field* tfield, bool init = false, string prefix = "");
+ string function_signature_async(t_function* tfunction, string prefix = "");
+ string function_signature(t_function* tfunction, string prefix = "");
+ string argument_list(t_struct* tstruct);
+ string type_to_enum(t_type* ttype);
+ string prop_name(t_field* tfield, bool suppress_mapping = false);
+ string get_enum_class_name(t_type* type);
+
+ static string correct_function_name_for_async(string const& function_name)
+ {
+ string const async_end = "Async";
+ size_t i = function_name.find(async_end);
+ if (i != string::npos)
+ {
+ return function_name + async_end;
+ }
+
+ return function_name;
+ }
+
+ /**
+ * \brief Search and replace "_args" substring in struct name if exist (for C# class naming)
+ * \param struct_name
+ * \return Modified struct name ("Struct_args" -> "StructArgs") or original name
+ */
+ static string check_and_correct_struct_name(const string& struct_name)
+ {
+ string args_end = "_args";
+ size_t i = struct_name.find(args_end);
+ if (i != string::npos)
+ {
+ string new_struct_name = struct_name;
+ new_struct_name.replace(i, args_end.length(), "Args");
+ return new_struct_name;
+ }
+
+ string result_end = "_result";
+ size_t j = struct_name.find(result_end);
+ if (j != string::npos)
+ {
+ string new_struct_name = struct_name;
+ new_struct_name.replace(j, result_end.length(), "Result");
+ return new_struct_name;
+ }
+
+ return struct_name;
+ }
+
+ static bool field_has_default(t_field* tfield) { return tfield->get_value() != NULL; }
+
+ static bool field_is_required(t_field* tfield) { return tfield->get_req() == t_field::T_REQUIRED; }
+
+ static bool type_can_be_null(t_type* ttype)
+ {
+ while (ttype->is_typedef())
+ {
+ ttype = static_cast<t_typedef*>(ttype)->get_type();
+ }
+
+ return ttype->is_container() || ttype->is_struct() || ttype->is_xception() || ttype->is_string();
+ }
+
+
+private:
+ string namespace_name_;
+ string namespace_dir_;
+
+ bool nullable_;
+ bool union_;
+ bool hashcode_;
+ bool serialize_;
+ bool wcf_;
+
+ string wcf_namespace_;
+ map<string, int> netcore_keywords;
+ vector<member_mapping_scope> member_mapping_scopes;
+
+ void init_keywords();
+ string normalize_name(string name);
+ string make_valid_csharp_identifier(string const& fromName);
+ void prepare_member_name_mapping(t_struct* tstruct);
+ void prepare_member_name_mapping(void* scope, const vector<t_field*>& members, const string& structname);
+ void cleanup_member_name_mapping(void* scope);
+ string get_mapped_member_name(string oldname);
+};
+
+void t_netcore_generator::init_generator()
+{
+ MKDIR(get_out_dir().c_str());
+
+ // for usage of csharp namespaces in thrift files (from files for csharp)
+ namespace_name_ = program_->get_namespace("netcore");
+ if (namespace_name_.empty())
+ {
+ namespace_name_ = program_->get_namespace("netcore");
+ }
+
+ string dir = namespace_name_;
+ string subdir = get_out_dir().c_str();
+ string::size_type loc;
+
+ while ((loc = dir.find(".")) != string::npos)
+ {
+ subdir = subdir + "/" + dir.substr(0, loc);
+ MKDIR(subdir.c_str());
+ dir = dir.substr(loc + 1);
+ }
+ if (dir.size() > 0)
+ {
+ subdir = subdir + "/" + dir;
+ MKDIR(subdir.c_str());
+ }
+
+ namespace_dir_ = subdir;
+ init_keywords();
+
+ while (!member_mapping_scopes.empty())
+ {
+ cleanup_member_name_mapping(member_mapping_scopes.back().scope_member);
+ }
+
+ pverbose(".NET Core options:\n");
+ pverbose("- nullable ... %s\n", (nullable_ ? "ON" : "off"));
+ pverbose("- union ...... %s\n", (union_ ? "ON" : "off"));
+ pverbose("- hashcode ... %s\n", (hashcode_ ? "ON" : "off"));
+ pverbose("- serialize .. %s\n", (serialize_ ? "ON" : "off"));
+ pverbose("- wcf ........ %s\n", (wcf_ ? "ON" : "off"));
+}
+
+string t_netcore_generator::normalize_name(string name)
+{
+ string tmp(name);
+ transform(tmp.begin(), tmp.end(), tmp.begin(), static_cast<int(*)(int)>(tolower));
+
+ // un-conflict keywords by prefixing with "@"
+ if (netcore_keywords.find(tmp) != netcore_keywords.end())
+ {
+ return "@" + name;
+ }
+
+ // no changes necessary
+ return name;
+}
+
+void t_netcore_generator::init_keywords()
+{
+ netcore_keywords.clear();
+
+ // C# keywords
+ netcore_keywords["abstract"] = 1;
+ netcore_keywords["as"] = 1;
+ netcore_keywords["base"] = 1;
+ netcore_keywords["bool"] = 1;
+ netcore_keywords["break"] = 1;
+ netcore_keywords["byte"] = 1;
+ netcore_keywords["case"] = 1;
+ netcore_keywords["catch"] = 1;
+ netcore_keywords["char"] = 1;
+ netcore_keywords["checked"] = 1;
+ netcore_keywords["class"] = 1;
+ netcore_keywords["const"] = 1;
+ netcore_keywords["continue"] = 1;
+ netcore_keywords["decimal"] = 1;
+ netcore_keywords["default"] = 1;
+ netcore_keywords["delegate"] = 1;
+ netcore_keywords["do"] = 1;
+ netcore_keywords["double"] = 1;
+ netcore_keywords["else"] = 1;
+ netcore_keywords["enum"] = 1;
+ netcore_keywords["event"] = 1;
+ netcore_keywords["explicit"] = 1;
+ netcore_keywords["extern"] = 1;
+ netcore_keywords["false"] = 1;
+ netcore_keywords["finally"] = 1;
+ netcore_keywords["fixed"] = 1;
+ netcore_keywords["float"] = 1;
+ netcore_keywords["for"] = 1;
+ netcore_keywords["foreach"] = 1;
+ netcore_keywords["goto"] = 1;
+ netcore_keywords["if"] = 1;
+ netcore_keywords["implicit"] = 1;
+ netcore_keywords["in"] = 1;
+ netcore_keywords["int"] = 1;
+ netcore_keywords["interface"] = 1;
+ netcore_keywords["internal"] = 1;
+ netcore_keywords["is"] = 1;
+ netcore_keywords["lock"] = 1;
+ netcore_keywords["long"] = 1;
+ netcore_keywords["namespace"] = 1;
+ netcore_keywords["new"] = 1;
+ netcore_keywords["null"] = 1;
+ netcore_keywords["object"] = 1;
+ netcore_keywords["operator"] = 1;
+ netcore_keywords["out"] = 1;
+ netcore_keywords["override"] = 1;
+ netcore_keywords["params"] = 1;
+ netcore_keywords["private"] = 1;
+ netcore_keywords["protected"] = 1;
+ netcore_keywords["public"] = 1;
+ netcore_keywords["readonly"] = 1;
+ netcore_keywords["ref"] = 1;
+ netcore_keywords["return"] = 1;
+ netcore_keywords["sbyte"] = 1;
+ netcore_keywords["sealed"] = 1;
+ netcore_keywords["short"] = 1;
+ netcore_keywords["sizeof"] = 1;
+ netcore_keywords["stackalloc"] = 1;
+ netcore_keywords["static"] = 1;
+ netcore_keywords["string"] = 1;
+ netcore_keywords["struct"] = 1;
+ netcore_keywords["switch"] = 1;
+ netcore_keywords["this"] = 1;
+ netcore_keywords["throw"] = 1;
+ netcore_keywords["true"] = 1;
+ netcore_keywords["try"] = 1;
+ netcore_keywords["typeof"] = 1;
+ netcore_keywords["uint"] = 1;
+ netcore_keywords["ulong"] = 1;
+ netcore_keywords["unchecked"] = 1;
+ netcore_keywords["unsafe"] = 1;
+ netcore_keywords["ushort"] = 1;
+ netcore_keywords["using"] = 1;
+ netcore_keywords["virtual"] = 1;
+ netcore_keywords["void"] = 1;
+ netcore_keywords["volatile"] = 1;
+ netcore_keywords["while"] = 1;
+
+ // C# contextual keywords
+ netcore_keywords["add"] = 1;
+ netcore_keywords["alias"] = 1;
+ netcore_keywords["ascending"] = 1;
+ netcore_keywords["async"] = 1;
+ netcore_keywords["await"] = 1;
+ netcore_keywords["descending"] = 1;
+ netcore_keywords["dynamic"] = 1;
+ netcore_keywords["from"] = 1;
+ netcore_keywords["get"] = 1;
+ netcore_keywords["global"] = 1;
+ netcore_keywords["group"] = 1;
+ netcore_keywords["into"] = 1;
+ netcore_keywords["join"] = 1;
+ netcore_keywords["let"] = 1;
+ netcore_keywords["orderby"] = 1;
+ netcore_keywords["partial"] = 1;
+ netcore_keywords["remove"] = 1;
+ netcore_keywords["select"] = 1;
+ netcore_keywords["set"] = 1;
+ netcore_keywords["value"] = 1;
+ netcore_keywords["var"] = 1;
+ netcore_keywords["where"] = 1;
+ netcore_keywords["yield"] = 1;
+}
+
+void t_netcore_generator::start_netcore_namespace(ofstream& out)
+{
+ if (!namespace_name_.empty())
+ {
+ out << "namespace " << namespace_name_ << endl;
+ scope_up(out);
+ }
+}
+
+void t_netcore_generator::end_netcore_namespace(ofstream& out)
+{
+ if (!namespace_name_.empty())
+ {
+ scope_down(out);
+ }
+}
+
+string t_netcore_generator::netcore_type_usings() const
+{
+ string namespaces =
+ "using System;\n"
+ "using System.Collections;\n"
+ "using System.Collections.Generic;\n"
+ "using System.Text;\n"
+ "using System.IO;\n"
+ "using System.Threading;\n"
+ "using System.Threading.Tasks;\n"
+ "using Thrift;\n"
+ "using Thrift.Collections;\n";
+
+ if (wcf_)
+ {
+ namespaces += "using System.ServiceModel;\n";
+ namespaces += "using System.Runtime.Serialization;\n";
+ }
+
+ return namespaces + endl;
+}
+
+string t_netcore_generator::netcore_thrift_usings() const
+{
+ string namespaces =
+ "using Thrift.Protocols;\n"
+ "using Thrift.Protocols.Entities;\n"
+ "using Thrift.Protocols.Utilities;\n"
+ "using Thrift.Transports;\n"
+ "using Thrift.Transports.Client;\n"
+ "using Thrift.Transports.Server;\n";
+
+ return namespaces + endl;
+}
+
+void t_netcore_generator::close_generator()
+{
+}
+
+void t_netcore_generator::generate_typedef(t_typedef* ttypedef)
+{
+ (void)ttypedef;
+}
+
+void t_netcore_generator::generate_enum(t_enum* tenum)
+{
+ int ic = indent_count();
+
+ string f_enum_name = namespace_dir_ + "/" + tenum->get_name() + ".cs";
+
+ ofstream f_enum;
+ f_enum.open(f_enum_name.c_str());
+ f_enum << autogen_comment() << endl;
+
+ start_netcore_namespace(f_enum);
+ generate_netcore_doc(f_enum, tenum);
+
+ f_enum << indent() << "public enum " << tenum->get_name() << endl;
+ scope_up(f_enum);
+
+ vector<t_enum_value*> constants = tenum->get_constants();
+ vector<t_enum_value*>::iterator c_iter;
+
+ for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter)
+ {
+ generate_netcore_doc(f_enum, *c_iter);
+ int value = (*c_iter)->get_value();
+ f_enum << indent() << (*c_iter)->get_name() << " = " << value << "," << endl;
+ }
+
+ scope_down(f_enum);
+ end_netcore_namespace(f_enum);
+ f_enum.close();
+
+ indent_validate(ic, "generate_enum");
+}
+
+void t_netcore_generator::generate_consts(vector<t_const*> consts)
+{
+ if (consts.empty())
+ {
+ return;
+ }
+
+ string f_consts_name = namespace_dir_ + '/' + program_name_ + ".Constants.cs";
+ ofstream f_consts;
+ f_consts.open(f_consts_name.c_str());
+
+ f_consts << autogen_comment() << netcore_type_usings() << endl;
+
+ start_netcore_namespace(f_consts);
+
+ f_consts << indent() << "public static class " << make_valid_csharp_identifier(program_name_) << "Constants" << endl;
+
+ scope_up(f_consts);
+
+ vector<t_const*>::iterator c_iter;
+ bool need_static_constructor = false;
+ for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter)
+ {
+ generate_netcore_doc(f_consts, *c_iter);
+ if (print_const_value(f_consts, (*c_iter)->get_name(), (*c_iter)->get_type(), (*c_iter)->get_value(), false))
+ {
+ need_static_constructor = true;
+ }
+ }
+
+ if (need_static_constructor)
+ {
+ print_const_constructor(f_consts, consts);
+ }
+
+ scope_down(f_consts);
+ end_netcore_namespace(f_consts);
+ f_consts.close();
+}
+
+void t_netcore_generator::print_const_def_value(ofstream& out, string name, t_type* type, t_const_value* value)
+{
+ if (type->is_struct() || type->is_xception())
+ {
+ const vector<t_field*>& fields = static_cast<t_struct*>(type)->get_members();
+ const map<t_const_value*, t_const_value*>& val = value->get_map();
+ vector<t_field*>::const_iterator f_iter;
+ map<t_const_value*, t_const_value*>::const_iterator v_iter;
+ prepare_member_name_mapping(static_cast<t_struct*>(type));
+
+ for (v_iter = val.begin(); v_iter != val.end(); ++v_iter)
+ {
+ t_field* field = NULL;
+
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+ {
+ if ((*f_iter)->get_name() == v_iter->first->get_string())
+ {
+ field = *f_iter;
+ }
+ }
+
+ if (field == NULL)
+ {
+ throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string();
+ }
+
+ t_type* field_type = field->get_type();
+
+ string val = render_const_value(out, name, field_type, v_iter->second);
+ out << indent() << name << "." << prop_name(field) << " = " << val << ";" << endl;
+ }
+
+ cleanup_member_name_mapping(static_cast<t_struct*>(type));
+ }
+ else if (type->is_map())
+ {
+ t_type* ktype = static_cast<t_map*>(type)->get_key_type();
+ t_type* vtype = static_cast<t_map*>(type)->get_val_type();
+ const map<t_const_value*, t_const_value*>& val = value->get_map();
+ map<t_const_value*, t_const_value*>::const_iterator v_iter;
+ for (v_iter = val.begin(); v_iter != val.end(); ++v_iter)
+ {
+ string key = render_const_value(out, name, ktype, v_iter->first);
+ string val = render_const_value(out, name, vtype, v_iter->second);
+ out << indent() << name << "[" << key << "]" << " = " << val << ";" << endl;
+ }
+ }
+ else if (type->is_list() || type->is_set())
+ {
+ t_type* etype;
+ if (type->is_list())
+ {
+ etype = static_cast<t_list*>(type)->get_elem_type();
+ }
+ else
+ {
+ etype = static_cast<t_set*>(type)->get_elem_type();
+ }
+
+ const vector<t_const_value*>& val = value->get_list();
+ vector<t_const_value*>::const_iterator v_iter;
+ for (v_iter = val.begin(); v_iter != val.end(); ++v_iter)
+ {
+ string val = render_const_value(out, name, etype, *v_iter);
+ out << indent() << name << ".Add(" << val << ");" << endl;
+ }
+ }
+}
+
+void t_netcore_generator::print_const_constructor(ofstream& out, vector<t_const*> consts)
+{
+ out << indent() << "static " << make_valid_csharp_identifier(program_name_).c_str() << "Constants()" << endl;
+ scope_up(out);
+
+ vector<t_const*>::iterator c_iter;
+ for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter)
+ {
+ string name = (*c_iter)->get_name();
+ t_type* type = (*c_iter)->get_type();
+ t_const_value* value = (*c_iter)->get_value();
+
+ print_const_def_value(out, name, type, value);
+ }
+ scope_down(out);
+}
+
+bool t_netcore_generator::print_const_value(ofstream& out, string name, t_type* type, t_const_value* value, bool in_static, bool defval, bool needtype)
+{
+ out << indent();
+ bool need_static_construction = !in_static;
+ while (type->is_typedef())
+ {
+ type = static_cast<t_typedef*>(type)->get_type();
+ }
+
+ if (!defval || needtype)
+ {
+ out << (in_static ? "" : type->is_base_type() ? "public const " : "public static ") << type_name(type) << " ";
+ }
+
+ if (type->is_base_type())
+ {
+ string v2 = render_const_value(out, name, type, value);
+ out << name << " = " << v2 << ";" << endl;
+ need_static_construction = false;
+ }
+ else if (type->is_enum())
+ {
+ out << name << " = " << type_name(type, false, true) << "." << value->get_identifier_name() << ";" << endl;
+ need_static_construction = false;
+ }
+ else if (type->is_struct() || type->is_xception())
+ {
+ out << name << " = new " << type_name(type) << "();" << endl;
+ }
+ else if (type->is_map())
+ {
+ out << name << " = new " << type_name(type, true, true) << "();" << endl;
+ }
+ else if (type->is_list() || type->is_set())
+ {
+ out << name << " = new " << type_name(type) << "();" << endl;
+ }
+
+ if (defval && !type->is_base_type() && !type->is_enum())
+ {
+ print_const_def_value(out, name, type, value);
+ }
+
+ return need_static_construction;
+}
+
+string t_netcore_generator::render_const_value(ofstream& out, string name, t_type* type, t_const_value* value)
+{
+ (void)name;
+ ostringstream render;
+
+ if (type->is_base_type())
+ {
+ t_base_type::t_base tbase = static_cast<t_base_type*>(type)->get_base();
+ switch (tbase)
+ {
+ case t_base_type::TYPE_STRING:
+ render << '"' << get_escaped_string(value) << '"';
+ break;
+ case t_base_type::TYPE_BOOL:
+ render << ((value->get_integer() > 0) ? "true" : "false");
+ break;
+ case t_base_type::TYPE_I8:
+ case t_base_type::TYPE_I16:
+ case t_base_type::TYPE_I32:
+ case t_base_type::TYPE_I64:
+ render << value->get_integer();
+ break;
+ case t_base_type::TYPE_DOUBLE:
+ if (value->get_type() == t_const_value::CV_INTEGER)
+ {
+ render << value->get_integer();
+ }
+ else
+ {
+ render << value->get_double();
+ }
+ break;
+ default:
+ throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase);
+ }
+ }
+ else if (type->is_enum())
+ {
+ render << type->get_name() << "." << value->get_identifier_name();
+ }
+ else
+ {
+ string t = tmp("tmp");
+ print_const_value(out, t, type, value, true, true, true);
+ render << t;
+ }
+
+ return render.str();
+}
+
+void t_netcore_generator::generate_struct(t_struct* tstruct)
+{
+ if (union_ && tstruct->is_union())
+ {
+ generate_netcore_union(tstruct);
+ }
+ else
+ {
+ generate_netcore_struct(tstruct, false);
+ }
+}
+
+void t_netcore_generator::generate_xception(t_struct* txception)
+{
+ generate_netcore_struct(txception, true);
+}
+
+void t_netcore_generator::generate_netcore_struct(t_struct* tstruct, bool is_exception)
+{
+ int ic = indent_count();
+
+ string f_struct_name = namespace_dir_ + "/" + (tstruct->get_name()) + ".cs";
+ ofstream f_struct;
+
+ f_struct.open(f_struct_name.c_str());
+
+ f_struct << autogen_comment() << netcore_type_usings() << netcore_thrift_usings() << endl;
+
+ generate_netcore_struct_definition(f_struct, tstruct, is_exception);
+
+ f_struct.close();
+
+ indent_validate(ic, "generate_netcore_struct");
+}
+
+void t_netcore_generator::generate_netcore_struct_definition(ofstream& out, t_struct* tstruct, bool is_exception, bool in_class, bool is_result)
+{
+ if (!in_class)
+ {
+ start_netcore_namespace(out);
+ }
+
+ out << endl;
+
+ generate_netcore_doc(out, tstruct);
+ prepare_member_name_mapping(tstruct);
+
+ if ((serialize_ || wcf_) && !is_exception)
+ {
+ out << indent() << "[DataContract(Namespace=\"" << wcf_namespace_ << "\")]" << endl;
+ }
+
+ bool is_final = tstruct->annotations_.find("final") != tstruct->annotations_.end();
+
+ string sharp_struct_name = check_and_correct_struct_name(normalize_name(tstruct->get_name()));
+
+ out << indent() << "public " << (is_final ? "sealed " : "") << "partial class " << sharp_struct_name << " : ";
+
+ if (is_exception)
+ {
+ out << "TException, ";
+ }
+
+ out << "TBase" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ const vector<t_field*>& members = tstruct->get_members();
+ vector<t_field*>::const_iterator m_iter;
+
+ // make private members with public Properties
+ for (m_iter = members.begin(); m_iter != members.end(); ++m_iter)
+ {
+ // if the field is requied, then we use auto-properties
+ if (!field_is_required((*m_iter)) && (!nullable_ || field_has_default((*m_iter))))
+ {
+ out << indent() << "private " << declare_field(*m_iter, false, "_") << endl;
+ }
+ }
+ out << endl;
+
+ bool has_non_required_fields = false;
+ bool has_non_required_default_value_fields = false;
+ bool has_required_fields = false;
+ for (m_iter = members.begin(); m_iter != members.end(); ++m_iter)
+ {
+ generate_netcore_doc(out, *m_iter);
+ generate_property(out, *m_iter, true, true);
+ bool is_required = field_is_required((*m_iter));
+ bool has_default = field_has_default((*m_iter));
+ if (is_required)
+ {
+ has_required_fields = true;
+ }
+ else
+ {
+ if (has_default)
+ {
+ has_non_required_default_value_fields = true;
+ }
+ has_non_required_fields = true;
+ }
+ }
+
+ bool generate_isset = (nullable_ && has_non_required_default_value_fields) || (!nullable_ && has_non_required_fields);
+ if (generate_isset)
+ {
+ out << endl;
+ if (serialize_ || wcf_)
+ {
+ out << indent() << "[DataMember(Order = 1)]" << endl;
+ }
+ out << indent() << "public Isset __isset;" << endl;
+ if (serialize_ || wcf_)
+ {
+ out << indent() << "[DataContract]" << endl;
+ }
+
+ out << indent() << "public struct Isset" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ for (m_iter = members.begin(); m_iter != members.end(); ++m_iter)
+ {
+ bool is_required = field_is_required((*m_iter));
+ bool has_default = field_has_default((*m_iter));
+ // if it is required, don't need Isset for that variable
+ // if it is not required, if it has a default value, we need to generate Isset
+ // if we are not nullable, then we generate Isset
+ if (!is_required && (!nullable_ || has_default))
+ {
+ if (serialize_ || wcf_)
+ {
+ out << indent() << "[DataMember]" << endl;
+ }
+ out << indent() << "public bool " << normalize_name((*m_iter)->get_name()) << ";" << endl;
+ }
+ }
+
+ indent_down();
+ out << indent() << "}" << endl << endl;
+
+ if (generate_isset && (serialize_ || wcf_))
+ {
+ out << indent() << "#region XmlSerializer support" << endl << endl;
+
+ for (m_iter = members.begin(); m_iter != members.end(); ++m_iter)
+ {
+ bool is_required = field_is_required(*m_iter);
+ bool has_default = field_has_default(*m_iter);
+ // if it is required, don't need Isset for that variable
+ // if it is not required, if it has a default value, we need to generate Isset
+ // if we are not nullable, then we generate Isset
+ if (!is_required && (!nullable_ || has_default))
+ {
+ out << indent() << "public bool ShouldSerialize" << prop_name(*m_iter) << "()" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "return __isset." << normalize_name((*m_iter)->get_name()) << ";" << endl;
+ indent_down();
+ out << indent() << "}" << endl << endl;
+ }
+ }
+
+ out << indent() << "#endregion XmlSerializer support" << endl << endl;
+ }
+ }
+
+ // We always want a default, no argument constructor for Reading
+ out << indent() << "public " << sharp_struct_name << "()" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ for (m_iter = members.begin(); m_iter != members.end(); ++m_iter)
+ {
+ t_type* t = (*m_iter)->get_type();
+ while (t->is_typedef())
+ {
+ t = static_cast<t_typedef*>(t)->get_type();
+ }
+ if ((*m_iter)->get_value() != NULL)
+ {
+ if (field_is_required((*m_iter)))
+ {
+ print_const_value(out, "this." + prop_name(*m_iter), t, (*m_iter)->get_value(), true, true);
+ }
+ else
+ {
+ print_const_value(out, "this._" + (*m_iter)->get_name(), t, (*m_iter)->get_value(), true, true);
+ // Optionals with defaults are marked set
+ out << indent() << "this.__isset." << normalize_name((*m_iter)->get_name()) << " = true;" << endl;
+ }
+ }
+ }
+ indent_down();
+ out << indent() << "}" << endl << endl;
+
+ if (has_required_fields)
+ {
+ out << indent() << "public " << sharp_struct_name << "(";
+ bool first = true;
+ for (m_iter = members.begin(); m_iter != members.end(); ++m_iter)
+ {
+ if (field_is_required(*m_iter))
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ out << ", ";
+ }
+ out << type_name((*m_iter)->get_type()) << " " << (*m_iter)->get_name();
+ }
+ }
+ out << ") : this()" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ for (m_iter = members.begin(); m_iter != members.end(); ++m_iter)
+ {
+ if (field_is_required(*m_iter))
+ {
+ out << indent() << "this." << prop_name(*m_iter) << " = " << (*m_iter)->get_name() << ";" << endl;
+ }
+ }
+
+ indent_down();
+ out << indent() << "}" << endl << endl;
+ }
+
+ generate_netcore_struct_reader(out, tstruct);
+ if (is_result)
+ {
+ generate_netcore_struct_result_writer(out, tstruct);
+ }
+ else
+ {
+ generate_netcore_struct_writer(out, tstruct);
+ }
+ if (hashcode_)
+ {
+ generate_netcore_struct_equals(out, tstruct);
+ generate_netcore_struct_hashcode(out, tstruct);
+ }
+ generate_netcore_struct_tostring(out, tstruct);
+
+ indent_down();
+ out << indent() << "}" << endl << endl;
+
+ // generate a corresponding WCF fault to wrap the exception
+ if ((serialize_ || wcf_) && is_exception)
+ {
+ generate_netcore_wcffault(out, tstruct);
+ }
+
+ cleanup_member_name_mapping(tstruct);
+ if (!in_class)
+ {
+ end_netcore_namespace(out);
+ }
+}
+
+void t_netcore_generator::generate_netcore_wcffault(ofstream& out, t_struct* tstruct)
+{
+ out << endl;
+ out << indent() << "[DataContract]" << endl;
+
+ bool is_final = tstruct->annotations_.find("final") != tstruct->annotations_.end();
+
+ out << indent() << "public " << (is_final ? "sealed " : "") << "partial class " << tstruct->get_name() << "Fault" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ const vector<t_field*>& members = tstruct->get_members();
+ vector<t_field*>::const_iterator m_iter;
+
+ // make private members with public Properties
+ for (m_iter = members.begin(); m_iter != members.end(); ++m_iter)
+ {
+ out << indent() << "private " << declare_field(*m_iter, false, "_") << endl;
+ }
+ out << endl;
+
+ for (m_iter = members.begin(); m_iter != members.end(); ++m_iter)
+ {
+ generate_property(out, *m_iter, true, false);
+ }
+
+ indent_down();
+ out << indent() << "}" << endl << endl;
+}
+
+void t_netcore_generator::generate_netcore_struct_reader(ofstream& out, t_struct* tstruct)
+{
+ out << indent() << "public async Task ReadAsync(TProtocol iprot, CancellationToken cancellationToken)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "iprot.IncrementRecursionDepth();" << endl
+ << indent() << "try" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ const vector<t_field*>& fields = tstruct->get_members();
+ vector<t_field*>::const_iterator f_iter;
+
+ // Required variables aren't in __isset, so we need tmp vars to check them
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+ {
+ if (field_is_required(*f_iter))
+ {
+ out << indent() << "bool isset_" << (*f_iter)->get_name() << " = false;" << endl;
+ }
+ }
+
+ out << indent() << "TField field;" << endl
+ << indent() << "await iprot.ReadStructBeginAsync(cancellationToken);" << endl
+ << indent() << "while (true)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "field = await iprot.ReadFieldBeginAsync(cancellationToken);" << endl
+ << indent() << "if (field.Type == TType.Stop)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "break;" << endl;
+ indent_down();
+ out << indent() << "}" << endl << endl
+ << indent() << "switch (field.ID)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+ {
+ bool is_required = field_is_required(*f_iter);
+ out << indent() << "case " << (*f_iter)->get_key() << ":" << endl;
+ indent_up();
+ out << indent() << "if (field.Type == " << type_to_enum((*f_iter)->get_type()) << ")" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ generate_deserialize_field(out, *f_iter);
+ if (is_required)
+ {
+ out << indent() << "isset_" << (*f_iter)->get_name() << " = true;" << endl;
+ }
+
+ indent_down();
+ out << indent() << "}" << endl
+ << indent() << "else" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);" << endl;
+ indent_down();
+ out << indent() << "}" << endl
+ << indent() << "break;" << endl;
+ indent_down();
+ }
+
+ out << indent() << "default: " << endl;
+ indent_up();
+ out << indent() << "await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);" << endl
+ << indent() << "break;" << endl;
+ indent_down();
+ indent_down();
+ out << indent() << "}" << endl
+ << endl
+ << indent() << "await iprot.ReadFieldEndAsync(cancellationToken);" << endl;
+ indent_down();
+ out << indent() << "}" << endl
+ << endl
+ << indent() << "await iprot.ReadStructEndAsync(cancellationToken);" << endl;
+
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+ {
+ if (field_is_required((*f_iter)))
+ {
+ out << indent() << "if (!isset_" << (*f_iter)->get_name() << ")" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "throw new TProtocolException(TProtocolException.INVALID_DATA);" << endl;
+ indent_down();
+ out << indent() << "}" << endl;
+ }
+ }
+
+ indent_down();
+ out << indent() << "}" << endl;
+ out << indent() << "finally" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "iprot.DecrementRecursionDepth();" << endl;
+ indent_down();
+ out << indent() << "}" << endl;
+ indent_down();
+ out << indent() << "}" << endl << endl;
+}
+
+void t_netcore_generator::generate_netcore_struct_writer(ofstream& out, t_struct* tstruct)
+{
+ out << indent() << "public async Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ out << indent() << "oprot.IncrementRecursionDepth();" << endl
+ << indent() << "try" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ string name = tstruct->get_name();
+ const vector<t_field*>& fields = tstruct->get_sorted_members();
+ vector<t_field*>::const_iterator f_iter;
+
+ out << indent() << "var struc = new TStruct(\"" << name << "\");" << endl
+ << indent() << "await oprot.WriteStructBeginAsync(struc, cancellationToken);" << endl;
+
+ if (fields.size() > 0)
+ {
+ out << indent() << "var field = new TField();" << endl;
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+ {
+ bool is_required = field_is_required(*f_iter);
+ bool has_default = field_has_default(*f_iter);
+ if (nullable_ && !has_default && !is_required)
+ {
+ out << indent() << "if (" << prop_name(*f_iter) << " != null)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ }
+ else if (!is_required)
+ {
+ bool null_allowed = type_can_be_null((*f_iter)->get_type());
+ if (null_allowed)
+ {
+ out << indent() << "if (" << prop_name(*f_iter) << " != null && __isset." << normalize_name((*f_iter)->get_name()) << ")" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ }
+ else
+ {
+ out << indent() << "if (__isset." << normalize_name((*f_iter)->get_name()) << ")" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ }
+ }
+ out << indent() << "field.Name = \"" << (*f_iter)->get_name() << "\";" << endl
+ << indent() << "field.Type = " << type_to_enum((*f_iter)->get_type()) << ";" << endl
+ << indent() << "field.ID = " << (*f_iter)->get_key() << ";" << endl
+ << indent() << "await oprot.WriteFieldBeginAsync(field, cancellationToken);" << endl;
+
+ generate_serialize_field(out, *f_iter);
+
+ out << indent() << "await oprot.WriteFieldEndAsync(cancellationToken);" << endl;
+ if (!is_required)
+ {
+ indent_down();
+ out << indent() << "}" << endl;
+ }
+ }
+ }
+
+ out << indent() << "await oprot.WriteFieldStopAsync(cancellationToken);" << endl
+ << indent() << "await oprot.WriteStructEndAsync(cancellationToken);" << endl;
+ indent_down();
+ out << indent() << "}" << endl
+ << indent() << "finally" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "oprot.DecrementRecursionDepth();" << endl;
+ indent_down();
+ out << indent() << "}" << endl;
+ indent_down();
+ out << indent() << "}" << endl << endl;
+}
+
+void t_netcore_generator::generate_netcore_struct_result_writer(ofstream& out, t_struct* tstruct)
+{
+ out << indent() << "public async Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ out << indent() << "oprot.IncrementRecursionDepth();" << endl
+ << indent() << "try" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ string name = tstruct->get_name();
+ const vector<t_field*>& fields = tstruct->get_sorted_members();
+ vector<t_field*>::const_iterator f_iter;
+
+ out << indent() << "var struc = new TStruct(\"" << name << "\");" << endl
+ << indent() << "await oprot.WriteStructBeginAsync(struc, cancellationToken);" << endl;
+
+ if (fields.size() > 0)
+ {
+ out << indent() << "var field = new TField();" << endl;
+ bool first = true;
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+ {
+ if (first)
+ {
+ first = false;
+ out << endl << indent() << "if";
+ }
+ else
+ {
+ out << indent() << "else if";
+ }
+
+ if (nullable_)
+ {
+ out << "(this." << prop_name((*f_iter)) << " != null)" << endl
+ << indent() << "{" << endl;
+ }
+ else
+ {
+ out << "(this.__isset." << normalize_name((*f_iter)->get_name()) << ")" << endl
+ << indent() << "{" << endl;
+ }
+ indent_up();
+
+ bool null_allowed = !nullable_ && type_can_be_null((*f_iter)->get_type());
+ if (null_allowed)
+ {
+ out << indent() << "if (" << prop_name(*f_iter) << " != null)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ }
+
+ out << indent() << "field.Name = \"" << prop_name(*f_iter) << "\";" << endl
+ << indent() << "field.Type = " << type_to_enum((*f_iter)->get_type()) << ";" << endl
+ << indent() << "field.ID = " << (*f_iter)->get_key() << ";" << endl
+ << indent() << "await oprot.WriteFieldBeginAsync(field, cancellationToken);" << endl;
+
+ generate_serialize_field(out, *f_iter);
+
+ out << indent() << "await oprot.WriteFieldEndAsync(cancellationToken);" << endl;
+
+ if (null_allowed)
+ {
+ indent_down();
+ out << indent() << "}" << endl;
+ }
+
+ indent_down();
+ out << indent() << "}" << endl;
+ }
+ }
+
+ out << indent() << "await oprot.WriteFieldStopAsync(cancellationToken);" << endl
+ << indent() << "await oprot.WriteStructEndAsync(cancellationToken);" << endl;
+ indent_down();
+ out << indent() << "}" << endl
+ << indent() << "finally" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "oprot.DecrementRecursionDepth();" << endl;
+ indent_down();
+ out << indent() << "}" << endl;
+ indent_down();
+ out << indent() << "}" << endl << endl;
+}
+
+void t_netcore_generator::generate_netcore_struct_tostring(ofstream& out, t_struct* tstruct)
+{
+ out << indent() << "public override string ToString()" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "var sb = new StringBuilder(\"" << tstruct->get_name() << "(\");" << endl;
+
+ const vector<t_field*>& fields = tstruct->get_members();
+ vector<t_field*>::const_iterator f_iter;
+
+ bool useFirstFlag = false;
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+ {
+ if (!field_is_required((*f_iter)))
+ {
+ out << indent() << "bool __first = true;" << endl;
+ useFirstFlag = true;
+ }
+ break;
+ }
+
+ bool had_required = false; // set to true after first required field has been processed
+
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+ {
+ bool is_required = field_is_required((*f_iter));
+ bool has_default = field_has_default((*f_iter));
+ if (nullable_ && !has_default && !is_required)
+ {
+ out << indent() << "if (" << prop_name((*f_iter)) << " != null)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ }
+ else if (!is_required)
+ {
+ bool null_allowed = type_can_be_null((*f_iter)->get_type());
+ if (null_allowed)
+ {
+ out << indent() << "if (" << prop_name((*f_iter)) << " != null && __isset." << normalize_name((*f_iter)->get_name()) << ")" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ }
+ else
+ {
+ out << indent() << "if (__isset." << normalize_name((*f_iter)->get_name()) << ")" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ }
+ }
+
+ if (useFirstFlag && (!had_required))
+ {
+ out << indent() << "if(!__first) { sb.Append(\", \"); }" << endl;
+ if (!is_required)
+ {
+ out << indent() << "__first = false;" << endl;
+ }
+ out << indent() << "sb.Append(\"" << prop_name(*f_iter) << ": \");" << endl;
+ }
+ else
+ {
+ out << indent() << "sb.Append(\", " << prop_name(*f_iter) << ": \");" << endl;
+ }
+
+ t_type* ttype = (*f_iter)->get_type();
+ if (ttype->is_xception() || ttype->is_struct())
+ {
+ out << indent() << "sb.Append(" << prop_name(*f_iter) << "== null ? \"<null>\" : " << prop_name(*f_iter) << ".ToString());" << endl;
+ }
+ else
+ {
+ out << indent() << "sb.Append(" << prop_name(*f_iter) << ");" << endl;
+ }
+
+ if (!is_required)
+ {
+ indent_down();
+ out << indent() << "}" << endl;
+ }
+ else
+ {
+ had_required = true; // now __first must be false, so we don't need to check it anymore
+ }
+ }
+
+ out << indent() << "sb.Append(\")\");" << endl
+ << indent() << "return sb.ToString();" << endl;
+ indent_down();
+ out << indent() << "}" << endl;
+}
+
+void t_netcore_generator::generate_netcore_union(t_struct* tunion)
+{
+ int ic = indent_count();
+
+ string f_union_name = namespace_dir_ + "/" + (tunion->get_name()) + ".cs";
+ ofstream f_union;
+
+ f_union.open(f_union_name.c_str());
+
+ f_union << autogen_comment() << netcore_type_usings() << netcore_thrift_usings() << endl;
+
+ generate_netcore_union_definition(f_union, tunion);
+
+ f_union.close();
+
+ indent_validate(ic, "generate_netcore_union.");
+}
+
+void t_netcore_generator::generate_netcore_union_definition(ofstream& out, t_struct* tunion)
+{
+ // Let's define the class first
+ start_netcore_namespace(out);
+
+ out << indent() << "public abstract partial class " << tunion->get_name() << " : TAbstractBase" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ out << indent() << "public abstract void Write(TProtocol protocol);" << endl
+ << indent() << "public readonly bool Isset;" << endl
+ << indent() << "public abstract object Data { get; }" << endl
+ << indent() << "protected " << tunion->get_name() << "(bool isset)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "Isset = isset;" << endl;
+ indent_down();
+ out << indent() << "}" << endl << endl;
+
+ out << indent() << "public class ___undefined : " << tunion->get_name() << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ out << indent() << "public override object Data { get { return null; } }" << endl
+ << indent() << "public ___undefined() : base(false) {}" << endl << endl;
+
+ out << indent() << "public override void Write(TProtocol protocol)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "throw new TProtocolException( TProtocolException.INVALID_DATA, \"Cannot persist an union type which is not set.\");" << endl;
+ indent_down();
+ out << indent() << "}" << endl << endl;
+ indent_down();
+ out << indent() << "}" << endl << endl;
+
+ const vector<t_field*>& fields = tunion->get_members();
+ vector<t_field*>::const_iterator f_iter;
+
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+ {
+ generate_netcore_union_class(out, tunion, (*f_iter));
+ }
+
+ generate_netcore_union_reader(out, tunion);
+
+ indent_down();
+ out << indent() << "}" << endl << endl;
+
+ end_netcore_namespace(out);
+}
+
+void t_netcore_generator::generate_netcore_union_class(ofstream& out, t_struct* tunion, t_field* tfield)
+{
+ out << indent() << "public class " << tfield->get_name() << " : " << tunion->get_name() << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ out << indent() << "private " << type_name(tfield->get_type()) << " _data;" << endl
+ << indent() << "public override object Data { get { return _data; } }" << endl
+ << indent() << "public " << tfield->get_name() << "(" << type_name(tfield->get_type()) << " data) : base(true)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "this._data = data;" << endl;
+ indent_down();
+ out << indent() << "}" << endl;
+
+ out << indent() << "public override async Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken) {" << endl;
+ indent_up();
+
+ out << indent() << "oprot.IncrementRecursionDepth();" << endl
+ << indent() << "try" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ out << indent() << "var struc = new TStruct(\"" << tunion->get_name() << "\");" << endl
+ << indent() << "await oprot.WriteStructBeginAsync(struc, cancellationToken);" << endl;
+
+ out << indent() << "var field = new TField();" << endl
+ << indent() << "field.Name = \"" << tfield->get_name() << "\";" << endl
+ << indent() << "field.Type = " << type_to_enum(tfield->get_type()) << ";" << endl
+ << indent() << "field.ID = " << tfield->get_key() << ";" << endl
+ << indent() << "await oprot.WriteFieldBeginAsync(field, cancellationToken);" << endl;
+
+ generate_serialize_field(out, tfield, "_data", true, true);
+
+ out << indent() << "await oprot.WriteFieldEndAsync(cancellationToken);" << endl
+ << indent() << "await oprot.WriteFieldStop(cancellationToken);" << endl
+ << indent() << "await oprot.WriteStructEnd(cancellationToken);" << endl;
+ indent_down();
+ out << indent() << "}" << endl
+ << indent() << "finally" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "oprot.DecrementRecursionDepth();" << endl;
+ indent_down();
+ out << indent() << "}" << endl;
+ out << indent() << "}" << endl;
+ indent_down();
+ out << indent() << "}" << endl << endl;
+}
+
+void t_netcore_generator::generate_netcore_struct_equals(ofstream& out, t_struct* tstruct)
+{
+ out << indent() << "public override bool Equals(object that)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "var other = that as " << type_name(tstruct) << ";" << endl
+ << indent() << "if (other == null) return false;" << endl
+ << indent() << "if (ReferenceEquals(this, other)) return true;" << endl;
+
+ const vector<t_field*>& fields = tstruct->get_members();
+ vector<t_field*>::const_iterator f_iter;
+
+ bool first = true;
+
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+ {
+ if (first)
+ {
+ first = false;
+ out << indent() << "return ";
+ indent_up();
+ }
+ else
+ {
+ out << endl;
+ out << indent() << "&& ";
+ }
+ if (!field_is_required((*f_iter)) && !(nullable_ && !field_has_default((*f_iter))))
+ {
+ out << "((__isset." << normalize_name((*f_iter)->get_name()) << " == other.__isset."
+ << normalize_name((*f_iter)->get_name()) << ") && ((!__isset."
+ << normalize_name((*f_iter)->get_name()) << ") || (";
+ }
+ t_type* ttype = (*f_iter)->get_type();
+ if (ttype->is_container() || (ttype->is_base_type() && (((t_base_type*)ttype)->is_binary())))
+ {
+ out << "TCollections.Equals(";
+ }
+ else
+ {
+ out << "System.Object.Equals(";
+ }
+ out << prop_name((*f_iter)) << ", other." << prop_name((*f_iter)) << ")";
+ if (!field_is_required((*f_iter)) && !(nullable_ && !field_has_default((*f_iter))))
+ {
+ out << ")))";
+ }
+ }
+ if (first)
+ {
+ out << indent() << "return true;" << endl;
+ }
+ else
+ {
+ out << ";" << endl;
+ indent_down();
+ }
+
+ indent_down();
+ out << indent() << "}" << endl << endl;
+}
+
+void t_netcore_generator::generate_netcore_struct_hashcode(ofstream& out, t_struct* tstruct)
+{
+ out << indent() << "public override int GetHashCode() {" << endl;
+ indent_up();
+
+ out << indent() << "int hashcode = 0;" << endl;
+ out << indent() << "unchecked {" << endl;
+ indent_up();
+
+ const vector<t_field*>& fields = tstruct->get_members();
+ vector<t_field*>::const_iterator f_iter;
+
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+ {
+ t_type* ttype = (*f_iter)->get_type();
+ out << indent() << "hashcode = (hashcode * 397) ^ ";
+ if (field_is_required((*f_iter)))
+ {
+ out << "(";
+ }
+ else if (nullable_)
+ {
+ out << "(" << prop_name((*f_iter)) << " == null ? 0 : ";
+ }
+ else
+ {
+ out << "(!__isset." << normalize_name((*f_iter)->get_name()) << " ? 0 : ";
+ }
+ if (ttype->is_container())
+ {
+ out << "(TCollections.GetHashCode(" << prop_name((*f_iter)) << "))";
+ }
+ else
+ {
+ out << "(" << prop_name((*f_iter)) << ".GetHashCode())";
+ }
+ out << ");" << endl;
+ }
+
+ indent_down();
+ out << indent() << "}" << endl;
+ out << indent() << "return hashcode;" << endl;
+
+ indent_down();
+ out << indent() << "}" << endl << endl;
+}
+
+void t_netcore_generator::generate_service(t_service* tservice)
+{
+ int ic = indent_count();
+
+ string f_service_name = namespace_dir_ + "/" + service_name_ + ".cs";
+ ofstream f_service;
+ f_service.open(f_service_name.c_str());
+
+ f_service << autogen_comment() << netcore_type_usings() << netcore_thrift_usings() << endl;
+
+ start_netcore_namespace(f_service);
+
+ f_service << indent() << "public partial class " << normalize_name(service_name_) << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ generate_service_interface(f_service, tservice);
+ generate_service_client(f_service, tservice);
+ generate_service_server(f_service, tservice);
+ generate_service_helpers(f_service, tservice);
+
+ indent_down();
+ f_service << indent() << "}" << endl;
+
+ end_netcore_namespace(f_service);
+ f_service.close();
+
+ indent_validate(ic, "generate_service.");
+}
+
+void t_netcore_generator::generate_service_interface(ofstream& out, t_service* tservice)
+{
+ string extends = "";
+ string extends_iface = "";
+ if (tservice->get_extends() != NULL)
+ {
+ extends = type_name(tservice->get_extends());
+ extends_iface = " : " + extends + ".IAsync";
+ }
+
+ //out << endl << endl;
+
+ generate_netcore_doc(out, tservice);
+
+ if (wcf_)
+ {
+ out << indent() << "[ServiceContract(Namespace=\"" << wcf_namespace_ << "\")]" << endl;
+ }
+
+ out << indent() << "public interface IAsync" << extends_iface << endl
+ << indent() << "{" << endl;
+
+ indent_up();
+ vector<t_function*> functions = tservice->get_functions();
+ vector<t_function*>::iterator f_iter;
+ for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter)
+ {
+ generate_netcore_doc(out, *f_iter);
+
+ // if we're using WCF, add the corresponding attributes
+ if (wcf_)
+ {
+ out << indent() << "[OperationContract]" << endl;
+
+ const vector<t_field*>& xceptions = (*f_iter)->get_xceptions()->get_members();
+ vector<t_field*>::const_iterator x_iter;
+ for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter)
+ {
+ out << indent() << "[FaultContract(typeof(" + type_name((*x_iter)->get_type(), false, false) + "Fault))]" << endl;
+ }
+ }
+
+ out << indent() << function_signature_async(*f_iter) << ";" << endl << endl;
+ }
+ indent_down();
+ out << indent() << "}" << endl << endl;
+}
+
+void t_netcore_generator::generate_service_helpers(ofstream& out, t_service* tservice)
+{
+ vector<t_function*> functions = tservice->get_functions();
+ vector<t_function*>::iterator f_iter;
+
+ for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter)
+ {
+ t_struct* ts = (*f_iter)->get_arglist();
+ generate_netcore_struct_definition(out, ts, false, true);
+ generate_function_helpers(out, *f_iter);
+ }
+}
+
+void t_netcore_generator::generate_service_client(ofstream& out, t_service* tservice)
+{
+ string extends = "";
+ string extends_client = "";
+ if (tservice->get_extends() != NULL)
+ {
+ extends = type_name(tservice->get_extends());
+ extends_client = extends + ".Client, ";
+ }
+ else
+ {
+ extends_client = "TBaseClient, IDisposable, ";
+ }
+
+ out << endl;
+
+ generate_netcore_doc(out, tservice);
+
+ out << indent() << "public class Client : " << extends_client << "IAsync" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ out << indent() << "public Client(TProtocol protocol) : this(protocol, protocol)" << endl
+ << indent() << "{" << endl
+ << indent() << "}" << endl
+ << endl
+ << indent() << "public Client(TProtocol inputProtocol, TProtocol outputProtocol) : base(inputProtocol, outputProtocol)"
+ << indent() << "{" << endl
+ << indent() << "}" << endl;
+
+ vector<t_function*> functions = tservice->get_functions();
+ vector<t_function*>::const_iterator functions_iterator;
+
+ for (functions_iterator = functions.begin(); functions_iterator != functions.end(); ++functions_iterator)
+ {
+ string function_name = correct_function_name_for_async((*functions_iterator)->get_name());
+
+ // async
+ out << indent() << "public async " << function_signature_async(*functions_iterator, "") << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ string argsname = (*functions_iterator)->get_name() + "Args";
+
+ out << indent() << "await OutputProtocol.WriteMessageBeginAsync(new TMessage(\"" << function_name
+ << "\", " << ((*functions_iterator)->is_oneway() ? "TMessageType.Oneway" : "TMessageType.Call") << ", SeqId), cancellationToken);" << endl
+ << indent() << endl
+ << indent() << "var args = new " << argsname << "();" << endl;
+
+ t_struct* arg_struct = (*functions_iterator)->get_arglist();
+ prepare_member_name_mapping(arg_struct);
+ const vector<t_field*>& fields = arg_struct->get_members();
+ vector<t_field*>::const_iterator fld_iter;
+
+ for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter)
+ {
+ out << indent() << "args." << prop_name(*fld_iter) << " = " << normalize_name((*fld_iter)->get_name()) << ";" << endl;
+ }
+
+ out << indent() << endl
+ << indent() << "await args.WriteAsync(OutputProtocol, cancellationToken);" << endl
+ << indent() << "await OutputProtocol.WriteMessageEndAsync(cancellationToken);" << endl
+ << indent() << "await OutputProtocol.Transport.FlushAsync(cancellationToken);" << endl;
+
+ if (!(*functions_iterator)->is_oneway())
+ {
+ string resultname = (*functions_iterator)->get_name() + "Result";
+ t_struct noargs(program_);
+ t_struct* xs = (*functions_iterator)->get_xceptions();
+ prepare_member_name_mapping(xs, xs->get_members(), resultname);
+
+ out << indent() << endl
+ << indent() << "var msg = await InputProtocol.ReadMessageBeginAsync(cancellationToken);" << endl
+ << indent() << "if (msg.Type == TMessageType.Exception)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ out << indent() << "var x = await TApplicationException.ReadAsync(InputProtocol, cancellationToken);" << endl
+ << indent() << "await InputProtocol.ReadMessageEndAsync(cancellationToken);" << endl
+ << indent() << "throw x;" << endl;
+ indent_down();
+
+ out << indent() << "}" << endl
+ << endl
+ << indent() << "var result = new " << resultname << "();" << endl
+ << indent() << "await result.ReadAsync(InputProtocol, cancellationToken);" << endl
+ << indent() << "await InputProtocol.ReadMessageEndAsync(cancellationToken);" << endl;
+
+ if (!(*functions_iterator)->get_returntype()->is_void())
+ {
+ if (nullable_)
+ {
+ if (type_can_be_null((*functions_iterator)->get_returntype()))
+ {
+ out << indent() << "if (result.Success != null)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "return result.Success;" << endl;
+ indent_down();
+ out << indent() << "}" << endl;
+ }
+ else
+ {
+ out << indent() << "if (result.Success.HasValue)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "return result.Success.Value;" << endl;
+ indent_down();
+ out << indent() << "}" << endl;
+ }
+ }
+ else
+ {
+ out << indent() << "if (result.__isset.success)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "return result.Success;" << endl;
+ indent_down();
+ out << indent() << "}" << endl;
+ }
+ }
+
+ const vector<t_field*>& xceptions = xs->get_members();
+ vector<t_field*>::const_iterator x_iter;
+ for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter)
+ {
+ if (nullable_)
+ {
+ out << indent() << "if (result." << prop_name(*x_iter) << " != null)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "throw result." << prop_name(*x_iter) << ";" << endl;
+ indent_down();
+ out << indent() << "}" << endl;
+ }
+ else
+ {
+ out << indent() << "if (result.__isset." << normalize_name((*x_iter)->get_name()) << ")" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "throw result." << prop_name(*x_iter) << ";" << endl;
+ indent_down();
+ out << indent() << "}" << endl;
+ }
+ }
+
+ if ((*functions_iterator)->get_returntype()->is_void())
+ {
+ out << indent() << "return;" << endl;
+ }
+ else
+ {
+ out << indent() << "throw new TApplicationException(TApplicationException.ExceptionType.MissingResult, \""
+ << function_name << " failed: unknown result\");" << endl;
+ }
+
+ cleanup_member_name_mapping((*functions_iterator)->get_xceptions());
+ indent_down();
+ out << indent() << "}" << endl << endl;
+ }
+ else
+ {
+ indent_down();
+ out << indent() << "}" << endl;
+ }
+ }
+
+ indent_down();
+ out << indent() << "}" << endl << endl;
+}
+
+void t_netcore_generator::generate_service_server(ofstream& out, t_service* tservice)
+{
+ vector<t_function*> functions = tservice->get_functions();
+ vector<t_function*>::iterator f_iter;
+
+ string extends = "";
+ string extends_processor = "";
+ if (tservice->get_extends() != NULL)
+ {
+ extends = type_name(tservice->get_extends());
+ extends_processor = extends + ".AsyncProcessor, ";
+ }
+
+ out << indent() << "public class AsyncProcessor : " << extends_processor << "ITAsyncProcessor" << endl
+ << indent() << "{" << endl;
+
+ indent_up();
+
+ out << indent() << "private IAsync _iAsync;" << endl
+ << endl
+ << indent() << "public AsyncProcessor(IAsync iAsync)";
+
+ if (!extends.empty())
+ {
+ out << " : base(iAsync)";
+ }
+
+ out << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ out << indent() << "if (iAsync == null) throw new ArgumentNullException(nameof(iAsync));" << endl
+ << endl
+ << indent() << "_iAsync = iAsync;" << endl;
+
+ for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter)
+ {
+ string function_name = (*f_iter)->get_name();
+ out << indent() << "processMap_[\"" << correct_function_name_for_async(function_name) << "\"] = " << function_name << "_ProcessAsync;" << endl;
+ }
+
+ indent_down();
+ out << indent() << "}" << endl
+ << endl;
+
+ if (extends.empty())
+ {
+ out << indent() << "protected delegate Task ProcessFunction(int seqid, TProtocol iprot, TProtocol oprot, CancellationToken cancellationToken);" << endl;
+ }
+
+ if (extends.empty())
+ {
+ out << indent() << "protected Dictionary<string, ProcessFunction> processMap_ = new Dictionary<string, ProcessFunction>();" << endl;
+ }
+
+ out << endl;
+
+ if (extends.empty())
+ {
+ out << indent() << "public async Task<bool> ProcessAsync(TProtocol iprot, TProtocol oprot)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "return await ProcessAsync(iprot, oprot, CancellationToken.None);" << endl;
+ indent_down();
+ out << indent() << "}" << endl << endl;
+
+ out << indent() << "public async Task<bool> ProcessAsync(TProtocol iprot, TProtocol oprot, CancellationToken cancellationToken)" << endl;
+ }
+ else
+ {
+ out << indent() << "public new async Task<bool> ProcessAsync(TProtocol iprot, TProtocol oprot)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "return await ProcessAsync(iprot, oprot, CancellationToken.None);" << endl;
+ indent_down();
+ out << indent() << "}" << endl << endl;
+
+ out << indent() << "public new async Task<bool> ProcessAsync(TProtocol iprot, TProtocol oprot, CancellationToken cancellationToken)" << endl;
+ }
+
+ out << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "try" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "var msg = await iprot.ReadMessageBeginAsync(cancellationToken);" << endl
+ << endl
+ << indent() << "ProcessFunction fn;" << endl
+ << indent() << "processMap_.TryGetValue(msg.Name, out fn);" << endl
+ << endl
+ << indent() << "if (fn == null)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "await TProtocolUtil.SkipAsync(iprot, TType.Struct, cancellationToken);" << endl
+ << indent() << "await iprot.ReadMessageEndAsync(cancellationToken);" << endl
+ << indent() << "var x = new TApplicationException (TApplicationException.ExceptionType.UnknownMethod, \"Invalid method name: '\" + msg.Name + \"'\");" << endl
+ << indent() << "await oprot.WriteMessageBeginAsync(new TMessage(msg.Name, TMessageType.Exception, msg.SeqID), cancellationToken);" << endl
+ << indent() << "await x.WriteAsync(oprot, cancellationToken);" << endl
+ << indent() << "await oprot.WriteMessageEndAsync(cancellationToken);" << endl
+ << indent() << "await oprot.Transport.FlushAsync(cancellationToken);" << endl
+ << indent() << "return true;" << endl;
+ indent_down();
+ out << indent() << "}" << endl
+ << endl
+ << indent() << "await fn(msg.SeqID, iprot, oprot, cancellationToken);" << endl
+ << endl;
+ indent_down();
+ out << indent() << "}" << endl;
+ out << indent() << "catch (IOException)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ out << indent() << "return false;" << endl;
+ indent_down();
+ out << indent() << "}" << endl
+ << endl
+ << indent() << "return true;" << endl;
+ indent_down();
+ out << indent() << "}" << endl << endl;
+
+ for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter)
+ {
+ generate_process_function_async(out, tservice, *f_iter);
+ }
+
+ indent_down();
+ out << indent() << "}" << endl << endl;
+}
+
+void t_netcore_generator::generate_function_helpers(ofstream& out, t_function* tfunction)
+{
+ if (tfunction->is_oneway())
+ {
+ return;
+ }
+
+ t_struct result(program_, tfunction->get_name() + "_result");
+ t_field success(tfunction->get_returntype(), "success", 0);
+ if (!tfunction->get_returntype()->is_void())
+ {
+ result.append(&success);
+ }
+
+ t_struct* xs = tfunction->get_xceptions();
+ const vector<t_field*>& fields = xs->get_members();
+ vector<t_field*>::const_iterator f_iter;
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+ {
+ result.append(*f_iter);
+ }
+
+ generate_netcore_struct_definition(out, &result, false, true, true);
+}
+
+void t_netcore_generator::generate_process_function_async(ofstream& out, t_service* tservice, t_function* tfunction)
+{
+ (void)tservice;
+ out << indent() << "public async Task " << tfunction->get_name()
+ << "_ProcessAsync(int seqid, TProtocol iprot, TProtocol oprot, CancellationToken cancellationToken)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ string argsname = tfunction->get_name() + "Args";
+ string resultname = tfunction->get_name() + "Result";
+
+ out << indent() << "var args = new " << argsname << "();" << endl
+ << indent() << "await args.ReadAsync(iprot, cancellationToken);" << endl
+ << indent() << "await iprot.ReadMessageEndAsync(cancellationToken);" << endl;
+
+ if (!tfunction->is_oneway())
+ {
+ out << indent() << "var result = new " << resultname << "();" << endl;
+ }
+
+ out << indent() << "try" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ t_struct* xs = tfunction->get_xceptions();
+ const vector<t_field*>& xceptions = xs->get_members();
+
+ if (xceptions.size() > 0)
+ {
+ out << indent() << "try" << endl
+ << indent() << "{" << endl;
+ indent_up();
+ }
+
+ t_struct* arg_struct = tfunction->get_arglist();
+ const vector<t_field*>& fields = arg_struct->get_members();
+ vector<t_field*>::const_iterator f_iter;
+
+ out << indent();
+ if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void())
+ {
+ out << "result.Success = ";
+ }
+
+ out << "await _iAsync." << normalize_name(tfunction->get_name()) << "Async(";
+
+ bool first = true;
+ prepare_member_name_mapping(arg_struct);
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ out << ", ";
+ }
+
+ out << "args." << prop_name(*f_iter);
+ if (nullable_ && !type_can_be_null((*f_iter)->get_type()))
+ {
+ out << ".Value";
+ }
+ }
+
+ cleanup_member_name_mapping(arg_struct);
+
+ if (!first)
+ {
+ out << ", ";
+ }
+
+ out << "cancellationToken);" << endl;
+
+ vector<t_field*>::const_iterator x_iter;
+
+ prepare_member_name_mapping(xs, xs->get_members(), resultname);
+ if (xceptions.size() > 0)
+ {
+ indent_down();
+ out << indent() << "}" << endl;
+
+ for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter)
+ {
+ out << indent() << "catch (" << type_name((*x_iter)->get_type(), false, false) << " " << (*x_iter)->get_name() << ")" << endl
+ << indent() << "{" << endl;
+
+ if (!tfunction->is_oneway())
+ {
+ indent_up();
+ out << indent() << "result." << prop_name(*x_iter) << " = " << (*x_iter)->get_name() << ";" << endl;
+ indent_down();
+ }
+ out << indent() << "}" << endl;
+ }
+ }
+
+ if (!tfunction->is_oneway())
+ {
+ out << indent() << "await oprot.WriteMessageBeginAsync(new TMessage(\""
+ << correct_function_name_for_async(tfunction->get_name()) << "\", TMessageType.Reply, seqid), cancellationToken); " << endl
+ << indent() << "await result.WriteAsync(oprot, cancellationToken);" << endl;
+ }
+ indent_down();
+
+ cleanup_member_name_mapping(xs);
+
+ out << indent() << "}" << endl
+ << indent() << "catch (TTransportException)" << endl
+ << indent() << "{" << endl
+ << indent() << " throw;" << endl
+ << indent() << "}" << endl
+ << indent() << "catch (Exception ex)" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ out << indent() << "Console.Error.WriteLine(\"Error occurred in processor:\");" << endl
+ << indent() << "Console.Error.WriteLine(ex.ToString());" << endl;
+
+ if (tfunction->is_oneway())
+ {
+ indent_down();
+ out << indent() << "}" << endl;
+ }
+ else
+ {
+ out << indent() << "var x = new TApplicationException(TApplicationException.ExceptionType.InternalError,\" Internal error.\");" << endl
+ << indent() << "await oprot.WriteMessageBeginAsync(new TMessage(\"" << correct_function_name_for_async(tfunction->get_name())
+ << "\", TMessageType.Exception, seqid), cancellationToken);" << endl
+ << indent() << "await x.WriteAsync(oprot, cancellationToken);" << endl;
+ indent_down();
+
+ out << indent() << "}" << endl
+ << indent() << "await oprot.WriteMessageEndAsync(cancellationToken);" << endl
+ << indent() << "await oprot.Transport.FlushAsync(cancellationToken);" << endl;
+ }
+
+ indent_down();
+ out << indent() << "}" << endl << endl;
+}
+
+void t_netcore_generator::generate_netcore_union_reader(ofstream& out, t_struct* tunion)
+{
+ // Thanks to THRIFT-1768, we don't need to check for required fields in the union
+ const vector<t_field*>& fields = tunion->get_members();
+ vector<t_field*>::const_iterator f_iter;
+
+ out << indent() << "public static " << tunion->get_name() << " Read(TProtocol iprot)" << endl;
+ scope_up(out);
+
+ out << indent() << "iprot.IncrementRecursionDepth();" << endl;
+ out << indent() << "try" << endl;
+ scope_up(out);
+
+ out << indent() << tunion->get_name() << " retval;" << endl;
+ out << indent() << "iprot.ReadStructBegin();" << endl;
+ out << indent() << "TField field = iprot.ReadFieldBegin();" << endl;
+ // we cannot have the first field be a stop -- we must have a single field defined
+ out << indent() << "if (field.Type == TType.Stop)" << endl;
+ scope_up(out);
+ out << indent() << "iprot.ReadFieldEnd();" << endl;
+ out << indent() << "retval = new ___undefined();" << endl;
+ scope_down(out);
+ out << indent() << "else" << endl;
+ scope_up(out);
+ out << indent() << "switch (field.ID)" << endl;
+ scope_up(out);
+
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+ {
+ out << indent() << "case " << (*f_iter)->get_key() << ":" << endl;
+ indent_up();
+ out << indent() << "if (field.Type == " << type_to_enum((*f_iter)->get_type()) << ") {" << endl;
+ indent_up();
+
+ out << indent() << type_name((*f_iter)->get_type()) << " temp;" << endl;
+ generate_deserialize_field(out, (*f_iter), "temp", true);
+ out << indent() << "retval = new " << (*f_iter)->get_name() << "(temp);" << endl;
+
+ indent_down();
+ out << indent() << "} else { " << endl << indent() << " TProtocolUtil.Skip(iprot, field.Type);"
+ << endl << indent() << " retval = new ___undefined();" << endl << indent() << "}" << endl
+ << indent() << "break;" << endl;
+ indent_down();
+ }
+
+ out << indent() << "default: " << endl;
+ indent_up();
+ out << indent() << "TProtocolUtil.Skip(iprot, field.Type);" << endl << indent()
+ << "retval = new ___undefined();" << endl;
+ out << indent() << "break;" << endl;
+ indent_down();
+
+ scope_down(out);
+
+ out << indent() << "iprot.ReadFieldEnd();" << endl;
+
+ out << indent() << "if (iprot.ReadFieldBegin().Type != TType.Stop)" << endl;
+ scope_up(out);
+ out << indent() << "throw new TProtocolException(TProtocolException.INVALID_DATA);" << endl;
+ scope_down(out);
+
+ // end of else for TStop
+ scope_down(out);
+ out << indent() << "iprot.ReadStructEnd();" << endl;
+ out << indent() << "return retval;" << endl;
+ indent_down();
+
+ scope_down(out);
+ out << indent() << "finally" << endl;
+ scope_up(out);
+ out << indent() << "iprot.DecrementRecursionDepth();" << endl;
+ scope_down(out);
+
+ out << indent() << "}" << endl << endl;
+}
+
+void t_netcore_generator::generate_deserialize_field(ofstream& out, t_field* tfield, string prefix, bool is_propertyless)
+{
+ t_type* type = tfield->get_type();
+ while (type->is_typedef())
+ {
+ type = static_cast<t_typedef*>(type)->get_type();
+ }
+
+ if (type->is_void())
+ {
+ throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name();
+ }
+
+ string name = prefix + (is_propertyless ? "" : prop_name(tfield));
+
+ if (type->is_struct() || type->is_xception())
+ {
+ generate_deserialize_struct(out, static_cast<t_struct*>(type), name);
+ }
+ else if (type->is_container())
+ {
+ generate_deserialize_container(out, type, name);
+ }
+ else if (type->is_base_type() || type->is_enum())
+ {
+ out << indent() << name << " = ";
+
+ if (type->is_enum())
+ {
+ out << "(" << type_name(type, false, true) << ")";
+ }
+
+ out << "await iprot.";
+
+ if (type->is_base_type())
+ {
+ t_base_type::t_base tbase = static_cast<t_base_type*>(type)->get_base();
+ switch (tbase)
+ {
+ case t_base_type::TYPE_VOID:
+ throw "compiler error: cannot serialize void field in a struct: " + name;
+ break;
+ case t_base_type::TYPE_STRING:
+ if (static_cast<t_base_type*>(type)->is_binary())
+ {
+ out << "ReadBinaryAsync(cancellationToken);";
+ }
+ else
+ {
+ out << "ReadStringAsync(cancellationToken);";
+ }
+ break;
+ case t_base_type::TYPE_BOOL:
+ out << "ReadBoolAsync(cancellationToken);";
+ break;
+ case t_base_type::TYPE_I8:
+ out << "ReadByteAsync(cancellationToken);";
+ break;
+ case t_base_type::TYPE_I16:
+ out << "ReadI16Async(cancellationToken);";
+ break;
+ case t_base_type::TYPE_I32:
+ out << "ReadI32Async(cancellationToken);";
+ break;
+ case t_base_type::TYPE_I64:
+ out << "ReadI64Async(cancellationToken);";
+ break;
+ case t_base_type::TYPE_DOUBLE:
+ out << "ReadDoubleAsync(cancellationToken);";
+ break;
+ default:
+ throw "compiler error: no C# name for base type " + t_base_type::t_base_name(tbase);
+ }
+ }
+ else if (type->is_enum())
+ {
+ out << "ReadI32Async(cancellationToken);";
+ }
+ out << endl;
+ }
+ else
+ {
+ printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n", tfield->get_name().c_str(), type_name(type).c_str());
+ }
+}
+
+void t_netcore_generator::generate_deserialize_struct(ofstream& out, t_struct* tstruct, string prefix)
+{
+ if (union_ && tstruct->is_union())
+ {
+ out << indent() << prefix << " = await " << type_name(tstruct) << ".ReadAsync(iprot, cancellationToken);" << endl;
+ }
+ else
+ {
+ out << indent() << prefix << " = new " << type_name(tstruct) << "();" << endl
+ << indent() << "await " << prefix << ".ReadAsync(iprot, cancellationToken);" << endl;
+ }
+}
+
+void t_netcore_generator::generate_deserialize_container(ofstream& out, t_type* ttype, string prefix)
+{
+ out << indent() << "{" << endl;
+ indent_up();
+
+ string obj;
+
+ if (ttype->is_map())
+ {
+ obj = tmp("_map");
+ }
+ else if (ttype->is_set())
+ {
+ obj = tmp("_set");
+ }
+ else if (ttype->is_list())
+ {
+ obj = tmp("_list");
+ }
+
+ out << indent() << prefix << " = new " << type_name(ttype, false, true) << "();" << endl;
+ if (ttype->is_map())
+ {
+ out << indent() << "TMap " << obj << " = await iprot.ReadMapBeginAsync(cancellationToken);" << endl;
+ }
+ else if (ttype->is_set())
+ {
+ out << indent() << "TSet " << obj << " = await iprot.ReadSetBeginAsync(cancellationToken);" << endl;
+ }
+ else if (ttype->is_list())
+ {
+ out << indent() << "TList " << obj << " = await iprot.ReadListBeginAsync(cancellationToken);" << endl;
+ }
+
+ string i = tmp("_i");
+ out << indent() << "for(int " << i << " = 0; " << i << " < " << obj << ".Count; ++" << i << ")" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ if (ttype->is_map())
+ {
+ generate_deserialize_map_element(out, static_cast<t_map*>(ttype), prefix);
+ }
+ else if (ttype->is_set())
+ {
+ generate_deserialize_set_element(out, static_cast<t_set*>(ttype), prefix);
+ }
+ else if (ttype->is_list())
+ {
+ generate_deserialize_list_element(out, static_cast<t_list*>(ttype), prefix);
+ }
+
+ indent_down();
+ out << indent() << "}" << endl;
+
+ if (ttype->is_map())
+ {
+ out << indent() << "await iprot.ReadMapEndAsync(cancellationToken);" << endl;
+ }
+ else if (ttype->is_set())
+ {
+ out << indent() << "await iprot.ReadSetEndAsync(cancellationToken);" << endl;
+ }
+ else if (ttype->is_list())
+ {
+ out << indent() << "await iprot.ReadListEndAsync(cancellationToken);" << endl;
+ }
+
+ indent_down();
+ out << indent() << "}" << endl;
+}
+
+void t_netcore_generator::generate_deserialize_map_element(ofstream& out, t_map* tmap, string prefix)
+{
+ string key = tmp("_key");
+ string val = tmp("_val");
+
+ t_field fkey(tmap->get_key_type(), key);
+ t_field fval(tmap->get_val_type(), val);
+
+ out << indent() << declare_field(&fkey) << endl;
+ out << indent() << declare_field(&fval) << endl;
+
+ generate_deserialize_field(out, &fkey);
+ generate_deserialize_field(out, &fval);
+
+ out << indent() << prefix << "[" << key << "] = " << val << ";" << endl;
+}
+
+void t_netcore_generator::generate_deserialize_set_element(ofstream& out, t_set* tset, string prefix)
+{
+ string elem = tmp("_elem");
+ t_field felem(tset->get_elem_type(), elem);
+
+ out << indent() << declare_field(&felem) << endl;
+
+ generate_deserialize_field(out, &felem);
+
+ out << indent() << prefix << ".Add(" << elem << ");" << endl;
+}
+
+void t_netcore_generator::generate_deserialize_list_element(ofstream& out, t_list* tlist, string prefix)
+{
+ string elem = tmp("_elem");
+ t_field felem(tlist->get_elem_type(), elem);
+
+ out << indent() << declare_field(&felem) << endl;
+
+ generate_deserialize_field(out, &felem);
+
+ out << indent() << prefix << ".Add(" << elem << ");" << endl;
+}
+
+void t_netcore_generator::generate_serialize_field(ofstream& out, t_field* tfield, string prefix, bool is_element, bool is_propertyless)
+{
+ t_type* type = tfield->get_type();
+ while (type->is_typedef())
+ {
+ type = static_cast<t_typedef*>(type)->get_type();
+ }
+
+ string name = prefix + (is_propertyless ? "" : prop_name(tfield));
+
+ if (type->is_void())
+ {
+ throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + name;
+ }
+
+ if (type->is_struct() || type->is_xception())
+ {
+ generate_serialize_struct(out, static_cast<t_struct*>(type), name);
+ }
+ else if (type->is_container())
+ {
+ generate_serialize_container(out, type, name);
+ }
+ else if (type->is_base_type() || type->is_enum())
+ {
+ out << indent() << "await oprot.";
+
+ string nullable_name = nullable_ && !is_element && !field_is_required(tfield) ? name + ".Value" : name;
+
+ if (type->is_base_type())
+ {
+ t_base_type::t_base tbase = static_cast<t_base_type*>(type)->get_base();
+ switch (tbase)
+ {
+ case t_base_type::TYPE_VOID:
+ throw "compiler error: cannot serialize void field in a struct: " + name;
+ case t_base_type::TYPE_STRING:
+ if (static_cast<t_base_type*>(type)->is_binary())
+ {
+ out << "WriteBinaryAsync(";
+ }
+ else
+ {
+ out << "WriteStringAsync(";
+ }
+ out << name << ", cancellationToken);";
+ break;
+ case t_base_type::TYPE_BOOL:
+ out << "WriteBoolAsync(" << nullable_name << ", cancellationToken);";
+ break;
+ case t_base_type::TYPE_I8:
+ out << "WriteByteAsync(" << nullable_name << ", cancellationToken);";
+ break;
+ case t_base_type::TYPE_I16:
+ out << "WriteI16Async(" << nullable_name << ", cancellationToken);";
+ break;
+ case t_base_type::TYPE_I32:
+ out << "WriteI32Async(" << nullable_name << ", cancellationToken);";
+ break;
+ case t_base_type::TYPE_I64:
+ out << "WriteI64Async(" << nullable_name << ", cancellationToken);";
+ break;
+ case t_base_type::TYPE_DOUBLE:
+ out << "WriteDoubleAsync(" << nullable_name << ", cancellationToken);";
+ break;
+ default:
+ throw "compiler error: no C# name for base type " + t_base_type::t_base_name(tbase);
+ }
+ }
+ else if (type->is_enum())
+ {
+ out << "WriteI32Async((int)" << nullable_name << ", cancellationToken);";
+ }
+ out << endl;
+ }
+ else
+ {
+ printf("DO NOT KNOW HOW TO SERIALIZE '%s%s' TYPE '%s'\n", prefix.c_str(), tfield->get_name().c_str(), type_name(type).c_str());
+ }
+}
+
+void t_netcore_generator::generate_serialize_struct(ofstream& out, t_struct* tstruct, string prefix)
+{
+ (void)tstruct;
+ out << indent() << "await " << prefix << ".WriteAsync(oprot, cancellationToken);" << endl;
+}
+
+void t_netcore_generator::generate_serialize_container(ofstream& out, t_type* ttype, string prefix)
+{
+ out << indent() << "{" << endl;
+ indent_up();
+
+ if (ttype->is_map())
+ {
+ out << indent() << "await oprot.WriteMapBeginAsync(new TMap(" << type_to_enum(static_cast<t_map*>(ttype)->get_key_type())
+ << ", " << type_to_enum(static_cast<t_map*>(ttype)->get_val_type()) << ", " << prefix
+ << ".Count), cancellationToken);" << endl;
+ }
+ else if (ttype->is_set())
+ {
+ out << indent() << "await oprot.WriteSetBeginAsync(new TSet(" << type_to_enum(static_cast<t_set*>(ttype)->get_elem_type())
+ << ", " << prefix << ".Count), cancellationToken);" << endl;
+ }
+ else if (ttype->is_list())
+ {
+ out << indent() << "await oprot.WriteListBeginAsync(new TList("
+ << type_to_enum(static_cast<t_list*>(ttype)->get_elem_type()) << ", " << prefix << ".Count), cancellationToken);"
+ << endl;
+ }
+
+ string iter = tmp("_iter");
+ if (ttype->is_map())
+ {
+ out << indent() << "foreach (" << type_name(static_cast<t_map*>(ttype)->get_key_type()) << " " << iter
+ << " in " << prefix << ".Keys)";
+ }
+ else if (ttype->is_set())
+ {
+ out << indent() << "foreach (" << type_name(static_cast<t_set*>(ttype)->get_elem_type()) << " " << iter
+ << " in " << prefix << ")";
+ }
+ else if (ttype->is_list())
+ {
+ out << indent() << "foreach (" << type_name(static_cast<t_list*>(ttype)->get_elem_type()) << " " << iter
+ << " in " << prefix << ")";
+ }
+
+ out << endl;
+ out << indent() << "{" << endl;
+ indent_up();
+
+ if (ttype->is_map())
+ {
+ generate_serialize_map_element(out, static_cast<t_map*>(ttype), iter, prefix);
+ }
+ else if (ttype->is_set())
+ {
+ generate_serialize_set_element(out, static_cast<t_set*>(ttype), iter);
+ }
+ else if (ttype->is_list())
+ {
+ generate_serialize_list_element(out, static_cast<t_list*>(ttype), iter);
+ }
+
+ indent_down();
+ out << indent() << "}" << endl;
+
+ if (ttype->is_map())
+ {
+ out << indent() << "await oprot.WriteMapEndAsync(cancellationToken);" << endl;
+ }
+ else if (ttype->is_set())
+ {
+ out << indent() << "await oprot.WriteSetEndAsync(cancellationToken);" << endl;
+ }
+ else if (ttype->is_list())
+ {
+ out << indent() << "await oprot.WriteListEndAsync(cancellationToken);" << endl;
+ }
+
+ indent_down();
+ out << indent() << "}" << endl;
+}
+
+void t_netcore_generator::generate_serialize_map_element(ofstream& out, t_map* tmap, string iter, string map)
+{
+ t_field kfield(tmap->get_key_type(), iter);
+ generate_serialize_field(out, &kfield, "", true);
+ t_field vfield(tmap->get_val_type(), map + "[" + iter + "]");
+ generate_serialize_field(out, &vfield, "", true);
+}
+
+void t_netcore_generator::generate_serialize_set_element(ofstream& out, t_set* tset, string iter)
+{
+ t_field efield(tset->get_elem_type(), iter);
+ generate_serialize_field(out, &efield, "", true);
+}
+
+void t_netcore_generator::generate_serialize_list_element(ofstream& out, t_list* tlist, string iter)
+{
+ t_field efield(tlist->get_elem_type(), iter);
+ generate_serialize_field(out, &efield, "", true);
+}
+
+void t_netcore_generator::generate_property(ofstream& out, t_field* tfield, bool isPublic, bool generateIsset)
+{
+ generate_netcore_property(out, tfield, isPublic, generateIsset, "_");
+}
+
+void t_netcore_generator::generate_netcore_property(ofstream& out, t_field* tfield, bool isPublic, bool generateIsset, string fieldPrefix)
+{
+ if ((serialize_ || wcf_) && isPublic)
+ {
+ out << indent() << "[DataMember(Order = 0)]" << endl;
+ }
+ bool has_default = field_has_default(tfield);
+ bool is_required = field_is_required(tfield);
+ if ((nullable_ && !has_default) || is_required)
+ {
+ out << indent() << (isPublic ? "public " : "private ") << type_name(tfield->get_type(), false, false, true, is_required) << " " << prop_name(tfield) << " { get; set; }" << endl;
+ }
+ else
+ {
+ out << indent() << (isPublic ? "public " : "private ") << type_name(tfield->get_type(), false, false, true) << " " << prop_name(tfield) << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ out << indent() << "get" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ bool use_nullable = false;
+ if (nullable_)
+ {
+ t_type* ttype = tfield->get_type();
+ while (ttype->is_typedef())
+ {
+ ttype = static_cast<t_typedef*>(ttype)->get_type();
+ }
+ if (ttype->is_base_type())
+ {
+ use_nullable = static_cast<t_base_type*>(ttype)->get_base() != t_base_type::TYPE_STRING;
+ }
+ }
+
+ out << indent() << "return " << fieldPrefix + tfield->get_name() << ";" << endl;
+ indent_down();
+ out << indent() << "}" << endl
+ << indent() << "set" << endl
+ << indent() << "{" << endl;
+ indent_up();
+
+ if (use_nullable)
+ {
+ if (generateIsset)
+ {
+ out << indent() << "__isset." << normalize_name(tfield->get_name()) << " = value.HasValue;" << endl;
+ }
+ out << indent() << "if (value.HasValue) this." << fieldPrefix + tfield->get_name() << " = value.Value;" << endl;
+ }
+ else
+ {
+ if (generateIsset)
+ {
+ out << indent() << "__isset." << normalize_name(tfield->get_name()) << " = true;" << endl;
+ }
+ out << indent() << "this." << fieldPrefix + tfield->get_name() << " = value;" << endl;
+ }
+
+ indent_down();
+ out << indent() << "}" << endl;
+ indent_down();
+ out << indent() << "}" << endl;
+ }
+ out << endl;
+}
+
+string t_netcore_generator::make_valid_csharp_identifier(string const& fromName)
+{
+ string str = fromName;
+ if (str.empty())
+ {
+ return str;
+ }
+
+ // tests rely on this
+ assert(('A' < 'Z') && ('a' < 'z') && ('0' < '9'));
+
+ // if the first letter is a number, we add an additional underscore in front of it
+ char c = str.at(0);
+ if (('0' <= c) && (c <= '9'))
+ {
+ str = "_" + str;
+ }
+
+ // following chars: letter, number or underscore
+ for (size_t i = 0; i < str.size(); ++i)
+ {
+ c = str.at(i);
+ if (('A' > c || c > 'Z') && ('a' > c || c > 'z') && ('0' > c || c > '9') && '_' != c)
+ {
+ str.replace(i, 1, "_");
+ }
+ }
+
+ return str;
+}
+
+void t_netcore_generator::cleanup_member_name_mapping(void* scope)
+{
+ if (member_mapping_scopes.empty())
+ {
+ throw "internal error: cleanup_member_name_mapping() no scope active";
+ }
+
+ member_mapping_scope& active = member_mapping_scopes.back();
+ if (active.scope_member != scope)
+ {
+ throw "internal error: cleanup_member_name_mapping() called for wrong struct";
+ }
+
+ member_mapping_scopes.pop_back();
+}
+
+string t_netcore_generator::get_mapped_member_name(string name)
+{
+ if (!member_mapping_scopes.empty())
+ {
+ member_mapping_scope& active = member_mapping_scopes.back();
+ map<string, string>::iterator iter = active.mapping_table.find(name);
+ if (active.mapping_table.end() != iter)
+ {
+ return iter->second;
+ }
+ }
+
+ pverbose("no mapping for member %s\n", name.c_str());
+ return name;
+}
+
+void t_netcore_generator::prepare_member_name_mapping(t_struct* tstruct)
+{
+ prepare_member_name_mapping(tstruct, tstruct->get_members(), tstruct->get_name());
+}
+
+void t_netcore_generator::prepare_member_name_mapping(void* scope, const vector<t_field*>& members, const string& structname)
+{
+ // begin new scope
+ member_mapping_scope dummy;
+ member_mapping_scopes.push_back(dummy);
+ member_mapping_scope& active = member_mapping_scopes.back();
+ active.scope_member = scope;
+
+ // current C# generator policy:
+ // - prop names are always rendered with an Uppercase first letter
+ // - struct names are used as given
+ std::set<string> used_member_names;
+ vector<t_field*>::const_iterator iter;
+
+ // prevent name conflicts with struct (CS0542 error)
+ used_member_names.insert(structname);
+
+ // prevent name conflicts with known methods (THRIFT-2942)
+ used_member_names.insert("Read");
+ used_member_names.insert("Write");
+
+ for (iter = members.begin(); iter != members.end(); ++iter)
+ {
+ string oldname = (*iter)->get_name();
+ string newname = prop_name(*iter, true);
+ while (true)
+ {
+ // new name conflicts with another member
+ if (used_member_names.find(newname) != used_member_names.end())
+ {
+ pverbose("struct %s: member %s conflicts with another member\n", structname.c_str(), newname.c_str());
+ newname += '_';
+ continue;
+ }
+
+ // add always, this helps us to detect edge cases like
+ // different spellings ("foo" and "Foo") within the same struct
+ pverbose("struct %s: member mapping %s => %s\n", structname.c_str(), oldname.c_str(), newname.c_str());
+ active.mapping_table[oldname] = newname;
+ used_member_names.insert(newname);
+ break;
+ }
+ }
+}
+
+string t_netcore_generator::prop_name(t_field* tfield, bool suppress_mapping)
+{
+ string name(tfield->get_name());
+ if (suppress_mapping)
+ {
+ name[0] = toupper(name[0]);
+ }
+ else
+ {
+ name = get_mapped_member_name(name);
+ }
+ return name;
+}
+
+string t_netcore_generator::type_name(t_type* ttype, bool in_container, bool in_init, bool in_param, bool is_required)
+{
+ (void)in_init;
+
+ while (ttype->is_typedef())
+ {
+ ttype = static_cast<t_typedef*>(ttype)->get_type();
+ }
+
+ if (ttype->is_base_type())
+ {
+ return base_type_name(static_cast<t_base_type*>(ttype), in_container, in_param, is_required);
+ }
+
+ if (ttype->is_map())
+ {
+ t_map* tmap = static_cast<t_map*>(ttype);
+ return "Dictionary<" + type_name(tmap->get_key_type(), true) + ", " + type_name(tmap->get_val_type(), true) + ">";
+ }
+
+ if (ttype->is_set())
+ {
+ t_set* tset = static_cast<t_set*>(ttype);
+ return "THashSet<" + type_name(tset->get_elem_type(), true) + ">";
+ }
+
+ if (ttype->is_list())
+ {
+ t_list* tlist = static_cast<t_list*>(ttype);
+ return "List<" + type_name(tlist->get_elem_type(), true) + ">";
+ }
+
+ t_program* program = ttype->get_program();
+ string postfix = (!is_required && nullable_ && in_param && ttype->is_enum()) ? "?" : "";
+ if (program != NULL && program != program_)
+ {
+ string ns = program->get_namespace("netcore");
+ if (!ns.empty())
+ {
+ return ns + "." + normalize_name(ttype->get_name()) + postfix;
+ }
+ }
+
+ return normalize_name(ttype->get_name()) + postfix;
+}
+
+string t_netcore_generator::base_type_name(t_base_type* tbase, bool in_container, bool in_param, bool is_required)
+{
+ (void)in_container;
+ string postfix = (!is_required && nullable_ && in_param) ? "?" : "";
+ switch (tbase->get_base())
+ {
+ case t_base_type::TYPE_VOID:
+ return "void";
+ case t_base_type::TYPE_STRING:
+ {
+ if (tbase->is_binary())
+ {
+ return "byte[]";
+ }
+ return "string";
+ }
+ case t_base_type::TYPE_BOOL:
+ return "bool" + postfix;
+ case t_base_type::TYPE_I8:
+ return "sbyte" + postfix;
+ case t_base_type::TYPE_I16:
+ return "short" + postfix;
+ case t_base_type::TYPE_I32:
+ return "int" + postfix;
+ case t_base_type::TYPE_I64:
+ return "long" + postfix;
+ case t_base_type::TYPE_DOUBLE:
+ return "double" + postfix;
+ default:
+ throw "compiler error: no C# name for base type " + t_base_type::t_base_name(tbase->get_base());
+ }
+}
+
+string t_netcore_generator::declare_field(t_field* tfield, bool init, string prefix)
+{
+ string result = type_name(tfield->get_type()) + " " + prefix + tfield->get_name();
+ if (init)
+ {
+ t_type* ttype = tfield->get_type();
+ while (ttype->is_typedef())
+ {
+ ttype = static_cast<t_typedef*>(ttype)->get_type();
+ }
+ if (ttype->is_base_type() && field_has_default(tfield))
+ {
+ ofstream dummy;
+ result += " = " + render_const_value(dummy, tfield->get_name(), ttype, tfield->get_value());
+ }
+ else if (ttype->is_base_type())
+ {
+ t_base_type::t_base tbase = static_cast<t_base_type*>(ttype)->get_base();
+ switch (tbase)
+ {
+ case t_base_type::TYPE_VOID:
+ throw "NO T_VOID CONSTRUCT";
+ case t_base_type::TYPE_STRING:
+ result += " = null";
+ break;
+ case t_base_type::TYPE_BOOL:
+ result += " = false";
+ break;
+ case t_base_type::TYPE_I8:
+ case t_base_type::TYPE_I16:
+ case t_base_type::TYPE_I32:
+ case t_base_type::TYPE_I64:
+ result += " = 0";
+ break;
+ case t_base_type::TYPE_DOUBLE:
+ result += " = (double)0";
+ break;
+ }
+ }
+ else if (ttype->is_enum())
+ {
+ result += " = (" + type_name(ttype, false, true) + ")0";
+ }
+ else if (ttype->is_container())
+ {
+ result += " = new " + type_name(ttype, false, true) + "()";
+ }
+ else
+ {
+ result += " = new " + type_name(ttype, false, true) + "()";
+ }
+ }
+ return result + ";";
+}
+
+string t_netcore_generator::function_signature(t_function* tfunction, string prefix)
+{
+ t_type* ttype = tfunction->get_returntype();
+ return type_name(ttype) + " " + normalize_name(prefix + tfunction->get_name()) + "(" + argument_list(tfunction->get_arglist()) + ")";
+}
+
+string t_netcore_generator::function_signature_async(t_function* tfunction, string prefix)
+{
+ t_type* ttype = tfunction->get_returntype();
+ string task = "Task";
+ if (!ttype->is_void())
+ {
+ task += "<" + type_name(ttype) + ">";
+ }
+
+ string result = task + " " + normalize_name(prefix + tfunction->get_name()) + "Async(";
+ string args = argument_list(tfunction->get_arglist());
+ result += args;
+ if (!args.empty())
+ {
+ result += ", ";
+ }
+ result += "CancellationToken cancellationToken)";
+
+ return result;
+}
+
+string t_netcore_generator::argument_list(t_struct* tstruct)
+{
+ string result = "";
+ const vector<t_field*>& fields = tstruct->get_members();
+ vector<t_field*>::const_iterator f_iter;
+ bool first = true;
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ result += ", ";
+ }
+ result += type_name((*f_iter)->get_type()) + " " + normalize_name((*f_iter)->get_name());
+ }
+ return result;
+}
+
+string t_netcore_generator::type_to_enum(t_type* type)
+{
+ while (type->is_typedef())
+ {
+ type = static_cast<t_typedef*>(type)->get_type();
+ }
+
+ if (type->is_base_type())
+ {
+ t_base_type::t_base tbase = static_cast<t_base_type*>(type)->get_base();
+ switch (tbase)
+ {
+ case t_base_type::TYPE_VOID:
+ throw "NO T_VOID CONSTRUCT";
+ case t_base_type::TYPE_STRING:
+ return "TType.String";
+ case t_base_type::TYPE_BOOL:
+ return "TType.Bool";
+ case t_base_type::TYPE_I8:
+ return "TType.Byte";
+ case t_base_type::TYPE_I16:
+ return "TType.I16";
+ case t_base_type::TYPE_I32:
+ return "TType.I32";
+ case t_base_type::TYPE_I64:
+ return "TType.I64";
+ case t_base_type::TYPE_DOUBLE:
+ return "TType.Double";
+ }
+ }
+ else if (type->is_enum())
+ {
+ return "TType.I32";
+ }
+ else if (type->is_struct() || type->is_xception())
+ {
+ return "TType.Struct";
+ }
+ else if (type->is_map())
+ {
+ return "TType.Map";
+ }
+ else if (type->is_set())
+ {
+ return "TType.Set";
+ }
+ else if (type->is_list())
+ {
+ return "TType.List";
+ }
+
+ throw "INVALID TYPE IN type_to_enum: " + type->get_name();
+}
+
+void t_netcore_generator::generate_netcore_docstring_comment(ofstream& out, string contents)
+{
+ docstring_comment(out, "/// <summary>" + endl, "/// ", contents, "/// </summary>" + endl);
+}
+
+void t_netcore_generator::generate_netcore_doc(ofstream& out, t_field* field)
+{
+ if (field->get_type()->is_enum())
+ {
+ string combined_message = field->get_doc() + endl + "<seealso cref=\"" + get_enum_class_name(field->get_type()) + "\"/>";
+ generate_netcore_docstring_comment(out, combined_message);
+ }
+ else
+ {
+ generate_netcore_doc(out, static_cast<t_doc*>(field));
+ }
+}
+
+void t_netcore_generator::generate_netcore_doc(ofstream& out, t_doc* tdoc)
+{
+ if (tdoc->has_doc())
+ {
+ generate_netcore_docstring_comment(out, tdoc->get_doc());
+ }
+}
+
+void t_netcore_generator::generate_netcore_doc(ofstream& out, t_function* tfunction)
+{
+ if (tfunction->has_doc())
+ {
+ stringstream ps;
+ const vector<t_field*>& fields = tfunction->get_arglist()->get_members();
+ vector<t_field*>::const_iterator p_iter;
+ for (p_iter = fields.begin(); p_iter != fields.end(); ++p_iter)
+ {
+ t_field* p = *p_iter;
+ ps << endl << "<param name=\"" << p->get_name() << "\">";
+ if (p->has_doc())
+ {
+ string str = p->get_doc();
+ str.erase(remove(str.begin(), str.end(), '\n'), str.end());
+ ps << str;
+ }
+ ps << "</param>";
+ }
+
+ docstring_comment(out,
+ "",
+ "/// ",
+ "<summary>" + endl + tfunction->get_doc() + "</summary>" + ps.str(),
+ "");
+ }
+}
+
+void t_netcore_generator::docstring_comment(ofstream& out, const string& comment_start, const string& line_prefix, const string& contents, const string& comment_end)
+{
+ if (comment_start != "")
+ {
+ out << indent() << comment_start;
+ }
+
+ stringstream docs(contents, std::ios_base::in);
+
+ while (!(docs.eof() || docs.fail()))
+ {
+ char line[1024];
+ docs.getline(line, 1024);
+
+ // Just prnt a newline when the line & prefix are empty.
+ if (strlen(line) == 0 && line_prefix == "" && !docs.eof())
+ {
+ out << endl;
+ }
+ else if (strlen(line) > 0 || !docs.eof())
+ { // skip the empty last line
+ out << indent() << line_prefix << line << endl;
+ }
+ }
+ if (comment_end != "")
+ {
+ out << indent() << comment_end;
+ }
+}
+
+string t_netcore_generator::get_enum_class_name(t_type* type)
+{
+ string package = "";
+ t_program* program = type->get_program();
+ if (program != NULL && program != program_)
+ {
+ package = program->get_namespace("netcore") + ".";
+ }
+ return package + type->get_name();
+}
+
+THRIFT_REGISTER_GENERATOR(
+ netcore,
+ "C#",
+ " wcf: Adds bindings for WCF to generated classes.\n"
+ " serial: Add serialization support to generated classes.\n"
+ " nullable: Use nullable types for properties.\n"
+ " hashcode: Generate a hashcode and equals implementation for classes.\n"
+ " union: Use new union typing, which includes a static read function for union types.\n"
+)
diff --git a/configure.ac b/configure.ac
index d3ea79172..dad10a72f 100755
--- a/configure.ac
+++ b/configure.ac
@@ -119,6 +119,7 @@ if test "$enable_libs" = "no"; then
with_ruby="no"
with_haskell="no"
with_haxe="no"
+ with_dotnetcore="no"
with_perl="no"
with_php="no"
with_php_extension="no"
@@ -420,6 +421,16 @@ fi
AM_CONDITIONAL(WITH_HAXE, [test "$have_haxe" = "yes"])
+AX_THRIFT_LIB(dotnetcore, [.NET Core], yes)
+if test "$with_dotnetcore" = "yes"; then
+ AC_PATH_PROG([DOTNETCORE], [dotnet])
+ if [[ -x "$DOTNETCORE" ]] ; then
+ AX_PROG_DOTNETCORE_VERSION( [1.0.0], have_dotnetcore="yes", have_dotnetcore="no")
+ fi
+fi
+AM_CONDITIONAL(WITH_DOTNETCORE, [test "$have_dotnetcore" = "yes"])
+
+
AX_THRIFT_LIB(d, [D], yes)
if test "$with_d" = "yes"; then
AX_DMD
@@ -757,6 +768,7 @@ AC_CONFIG_FILES([
lib/js/test/Makefile
lib/json/Makefile
lib/json/test/Makefile
+ lib/netcore/Makefile
lib/nodejs/Makefile
lib/perl/Makefile
lib/perl/test/Makefile
@@ -778,6 +790,7 @@ AC_CONFIG_FILES([
test/haxe/Makefile
test/hs/Makefile
test/lua/Makefile
+ test/netcore/Makefile
test/php/Makefile
test/dart/Makefile
test/perl/Makefile
@@ -794,6 +807,7 @@ AC_CONFIG_FILES([
tutorial/hs/Makefile
tutorial/java/Makefile
tutorial/js/Makefile
+ tutorial/netcore/Makefile
tutorial/nodejs/Makefile
tutorial/dart/Makefile
tutorial/py/Makefile
@@ -846,6 +860,7 @@ echo "Building C++ Library ......... : $have_cpp"
echo "Building C (GLib) Library .... : $have_c_glib"
echo "Building Java Library ........ : $have_java"
echo "Building C# Library .......... : $have_csharp"
+echo "Building .NET Core Library ... : $have_dotnetcore"
echo "Building Python Library ...... : $have_python"
echo "Building Ruby Library ........ : $have_ruby"
echo "Building Haxe Library ........ : $have_haxe"
@@ -879,6 +894,12 @@ if test "$have_csharp" = "yes" ; then
echo "C# Library:"
echo " Using .NET 3.5 ............ : $net_3_5"
fi
+if test "$have_dotnetcore" = "yes" ; then
+ echo
+ echo ".NET Core Library:"
+ echo " Using .NET Core ........... : $DOTNETCORE"
+ echo " Using .NET Core version ... : $DOTNETCORE_VERSION"
+fi
if test "$have_python" = "yes" ; then
echo
echo "Python Library:"
diff --git a/contrib/fb303/if/fb303.thrift b/contrib/fb303/if/fb303.thrift
index 66c831527..89bd6eb7b 100644
--- a/contrib/fb303/if/fb303.thrift
+++ b/contrib/fb303/if/fb303.thrift
@@ -24,6 +24,7 @@
namespace java com.facebook.fb303
namespace cpp facebook.fb303
namespace perl Facebook.FB303
+namespace netcore Facebook.FB303.Test
/**
* Common status reporting mechanism across all services
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 5f17fca88..21d807a3a 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -71,6 +71,10 @@ if WITH_DART
SUBDIRS += dart
endif
+if WITH_DOTNETCORE
+SUBDIRS += netcore
+endif
+
if WITH_GO
SUBDIRS += go
endif
diff --git a/lib/netcore/Makefile.am b/lib/netcore/Makefile.am
new file mode 100644
index 000000000..99f86b8c7
--- /dev/null
+++ b/lib/netcore/Makefile.am
@@ -0,0 +1,108 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+SUBDIRS = .
+
+THRIFT = $(top_builddir)/compiler/cpp/thrift
+
+TESTDIR = Tests/Thrift.PublicInterfaces.Compile.Tests
+GENDIR = $(TESTDIR)/gen-netcore
+
+THRIFTCODE = \
+ Thrift/ITAsyncProcessor.cs \
+ Thrift/ITProcessorFactory.cs \
+ Thrift/SingletonTProcessorFactory.cs \
+ Thrift/TApplicationException.cs \
+ Thrift/TBaseClient.cs \
+ Thrift/TException.cs \
+ Thrift/TMultiplexedProcessor.cs \
+ Thrift/Collections/TCollections.cs \
+ Thrift/Collections/THashSet.cs \
+ Thrift/Properties/AssemblyInfo.cs \
+ Thrift/Protocols/ITProtocolFactory.cs \
+ Thrift/Protocols/TAbstractBase.cs \
+ Thrift/Protocols/TBase.cs \
+ Thrift/Protocols/TBinaryProtocol.cs \
+ Thrift/Protocols/TCompactProtocol.cs \
+ Thrift/Protocols/TJSONProtocol.cs \
+ Thrift/Protocols/TMultiplexedProtocol.cs \
+ Thrift/Protocols/TProtocol.cs \
+ Thrift/Protocols/TProtocolDecorator.cs \
+ Thrift/Protocols/TProtocolException.cs \
+ Thrift/Protocols/Entities/TField.cs \
+ Thrift/Protocols/Entities/TList.cs \
+ Thrift/Protocols/Entities/TMap.cs \
+ Thrift/Protocols/Entities/TMessage.cs \
+ Thrift/Protocols/Entities/TMessageType.cs \
+ Thrift/Protocols/Entities/TSet.cs \
+ Thrift/Protocols/Entities/TStruct.cs \
+ Thrift/Protocols/Entities/TType.cs \
+ Thrift/Protocols/Utilities/TBase64Utils.cs \
+ Thrift/Protocols/Utilities/TProtocolUtil.cs \
+ Thrift/Server/AsyncBaseServer.cs \
+ Thrift/Server/TBaseServer.cs \
+ Thrift/Server/TServerEventHandler.cs \
+ Thrift/Transports/TClientTransport.cs \
+ Thrift/Transports/TServerTransport.cs \
+ Thrift/Transports/TTransportException.cs \
+ Thrift/Transports/TTransportFactory.cs \
+ Thrift/Transports/Client/TBufferedClientTransport.cs \
+ Thrift/Transports/Client/TFramedClientTransport.cs \
+ Thrift/Transports/Client/THttpClientTransport.cs \
+ Thrift/Transports/Client/TMemoryBufferClientTransport.cs \
+ Thrift/Transports/Client/TNamedPipeClientTransport.cs \
+ Thrift/Transports/Client/TSocketClientTransport.cs \
+ Thrift/Transports/Client/TStreamClientTransport.cs \
+ Thrift/Transports/Client/TTlsSocketClientTransport.cs \
+ Thrift/Transports/Server/THttpServerTransport.cs \
+ Thrift/Transports/Server/TNamedPipeServerTransport.cs \
+ Thrift/Transports/Server/TServerSocketTransport.cs \
+ Thrift/Transports/Server/TTlsServerSocketTransport.cs
+
+all-local: \
+ Thrift.dll
+
+Thrift.dll: $(THRIFTCODE)
+ $(MKDIR_P) $(GENDIR)
+ $(THRIFT) -gen netcore:wcf -r -out $(GENDIR) $(TESTDIR)/CassandraTest.thrift
+ $(THRIFT) -gen netcore:wcf -r -out $(GENDIR) $(top_srcdir)/test/ThriftTest.thrift
+ $(THRIFT) -gen netcore:wcf -r -out $(GENDIR) $(top_srcdir)/contrib/fb303/if/fb303.thrift
+ $(DOTNETCORE) --info
+ $(DOTNETCORE) restore
+ $(DOTNETCORE) build **/*/project.json -r win10-x64
+ $(DOTNETCORE) build **/*/project.json -r osx.10.11-x64
+ $(DOTNETCORE) build **/*/project.json -r ubuntu.16.04-x64
+
+clean-local:
+ $(RM) Thrift.dll
+ $(RM) -r $(GENDIR)
+ $(RM) -r Thrift/bin
+ $(RM) -r Thrift/obj
+ $(RM) -r Tests/Thrift.PublicInterfaces.Compile.Tests/bin
+ $(RM) -r Tests/Thrift.PublicInterfaces.Compile.Tests/obj
+
+EXTRA_DIST = \
+ $(THRIFTCODE) \
+ global.json \
+ Thrift.sln \
+ Thrift/project.json \
+ Thrift/Thrift.xproj \
+ Tests \
+ README.md
+
diff --git a/lib/netcore/README.md b/lib/netcore/README.md
new file mode 100644
index 000000000..ae926e978
--- /dev/null
+++ b/lib/netcore/README.md
@@ -0,0 +1,21 @@
+# Apache Thrift net-core-lib
+
+Thrift client library ported to Microsoft .Net Core
+
+# How to build
+
+* Download the latest version of dotnet from https://www.microsoft.com/net/core (it can be https://go.microsoft.com/fwlink/?LinkID=809122 in case of VS Code)
+* Install downloaded version of dotnet
+* Clone repo
+* Run **build.sh** or **build.cmd** from the root of cloned repository
+* Check tests in **src/Tests** folder
+* Continue with /tutorials/netcore
+
+#Notes
+
+* No Silverlight suport, no longer supported by Microsoft
+
+#Known issues
+
+* Framed transport not fully implemenented yet
+
diff --git a/lib/netcore/Tests/Thrift.PublicInterfaces.Compile.Tests/CassandraTest.thrift b/lib/netcore/Tests/Thrift.PublicInterfaces.Compile.Tests/CassandraTest.thrift
new file mode 100644
index 000000000..4b92720c2
--- /dev/null
+++ b/lib/netcore/Tests/Thrift.PublicInterfaces.Compile.Tests/CassandraTest.thrift
@@ -0,0 +1,705 @@
+#!/usr/local/bin/thrift --java --php --py
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+# *** PLEASE REMEMBER TO EDIT THE VERSION CONSTANT WHEN MAKING CHANGES ***
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#
+# Interface definition for Cassandra Service
+#
+
+namespace netcore Apache.Cassandra.Test
+
+# Thrift.rb has a bug where top-level modules that include modules
+# with the same name are not properly referenced, so we can't do
+# Cassandra::Cassandra::Client.
+namespace rb CassandraThrift
+
+# The API version (NOT the product version), composed as a dot delimited
+# string with major, minor, and patch level components.
+#
+# - Major: Incremented for backward incompatible changes. An example would
+# be changes to the number or disposition of method arguments.
+# - Minor: Incremented for backward compatible changes. An example would
+# be the addition of a new (optional) method.
+# - Patch: Incremented for bug fixes. The patch level should be increased
+# for every edit that doesn't result in a change to major/minor.
+#
+# See the Semantic Versioning Specification (SemVer) http://semver.org.
+const string VERSION = "19.24.0"
+
+
+#
+# data structures
+#
+
+/** Basic unit of data within a ColumnFamily.
+ * @param name, the name by which this column is set and retrieved. Maximum 64KB long.
+ * @param value. The data associated with the name. Maximum 2GB long, but in practice you should limit it to small numbers of MB (since Thrift must read the full value into memory to operate on it).
+ * @param timestamp. The timestamp is used for conflict detection/resolution when two columns with same name need to be compared.
+ * @param ttl. An optional, positive delay (in seconds) after which the column will be automatically deleted.
+ */
+struct Column {
+ 1: required binary name,
+ 2: optional binary value,
+ 3: optional i64 timestamp,
+ 4: optional i32 ttl,
+}
+
+/** A named list of columns.
+ * @param name. see Column.name.
+ * @param columns. A collection of standard Columns. The columns within a super column are defined in an adhoc manner.
+ * Columns within a super column do not have to have matching structures (similarly named child columns).
+ */
+struct SuperColumn {
+ 1: required binary name,
+ 2: required list<Column> columns,
+}
+
+struct CounterColumn {
+ 1: required binary name,
+ 2: required i64 value
+}
+
+struct CounterSuperColumn {
+ 1: required binary name,
+ 2: required list<CounterColumn> columns
+}
+
+/**
+ Methods for fetching rows/records from Cassandra will return either a single instance of ColumnOrSuperColumn or a list
+ of ColumnOrSuperColumns (get_slice()). If you're looking up a SuperColumn (or list of SuperColumns) then the resulting
+ instances of ColumnOrSuperColumn will have the requested SuperColumn in the attribute super_column. For queries resulting
+ in Columns, those values will be in the attribute column. This change was made between 0.3 and 0.4 to standardize on
+ single query methods that may return either a SuperColumn or Column.
+
+ If the query was on a counter column family, you will either get a counter_column (instead of a column) or a
+ counter_super_column (instead of a super_column)
+
+ @param column. The Column returned by get() or get_slice().
+ @param super_column. The SuperColumn returned by get() or get_slice().
+ @param counter_column. The Counterolumn returned by get() or get_slice().
+ @param counter_super_column. The CounterSuperColumn returned by get() or get_slice().
+ */
+struct ColumnOrSuperColumn {
+ 1: optional Column column,
+ 2: optional SuperColumn super_column,
+ 3: optional CounterColumn counter_column,
+ 4: optional CounterSuperColumn counter_super_column
+}
+
+
+#
+# Exceptions
+# (note that internal server errors will raise a TApplicationException, courtesy of Thrift)
+#
+
+/** A specific column was requested that does not exist. */
+exception NotFoundException {
+}
+
+/** Invalid request could mean keyspace or column family does not exist, required parameters are missing, or a parameter is malformed.
+ why contains an associated error message.
+*/
+exception InvalidRequestException {
+ 1: required string why
+}
+
+/** Not all the replicas required could be created and/or read. */
+exception UnavailableException {
+}
+
+/** RPC timeout was exceeded. either a node failed mid-operation, or load was too high, or the requested op was too large. */
+exception TimedOutException {
+}
+
+/** invalid authentication request (invalid keyspace, user does not exist, or credentials invalid) */
+exception AuthenticationException {
+ 1: required string why
+}
+
+/** invalid authorization request (user does not have access to keyspace) */
+exception AuthorizationException {
+ 1: required string why
+}
+
+/** schemas are not in agreement across all nodes */
+exception SchemaDisagreementException {
+}
+
+
+#
+# service api
+#
+/**
+ * The ConsistencyLevel is an enum that controls both read and write
+ * behavior based on the ReplicationFactor of the keyspace. The
+ * different consistency levels have different meanings, depending on
+ * if you're doing a write or read operation.
+ *
+ * If W + R > ReplicationFactor, where W is the number of nodes to
+ * block for on write, and R the number to block for on reads, you
+ * will have strongly consistent behavior; that is, readers will
+ * always see the most recent write. Of these, the most interesting is
+ * to do QUORUM reads and writes, which gives you consistency while
+ * still allowing availability in the face of node failures up to half
+ * of <ReplicationFactor>. Of course if latency is more important than
+ * consistency then you can use lower values for either or both.
+ *
+ * Some ConsistencyLevels (ONE, TWO, THREE) refer to a specific number
+ * of replicas rather than a logical concept that adjusts
+ * automatically with the replication factor. Of these, only ONE is
+ * commonly used; TWO and (even more rarely) THREE are only useful
+ * when you care more about guaranteeing a certain level of
+ * durability, than consistency.
+ *
+ * Write consistency levels make the following guarantees before reporting success to the client:
+ * ANY Ensure that the write has been written once somewhere, including possibly being hinted in a non-target node.
+ * ONE Ensure that the write has been written to at least 1 node's commit log and memory table
+ * TWO Ensure that the write has been written to at least 2 node's commit log and memory table
+ * THREE Ensure that the write has been written to at least 3 node's commit log and memory table
+ * QUORUM Ensure that the write has been written to <ReplicationFactor> / 2 + 1 nodes
+ * LOCAL_QUORUM Ensure that the write has been written to <ReplicationFactor> / 2 + 1 nodes, within the local datacenter (requires NetworkTopologyStrategy)
+ * EACH_QUORUM Ensure that the write has been written to <ReplicationFactor> / 2 + 1 nodes in each datacenter (requires NetworkTopologyStrategy)
+ * ALL Ensure that the write is written to <code>&lt;ReplicationFactor&gt;</code> nodes before responding to the client.
+ *
+ * Read consistency levels make the following guarantees before returning successful results to the client:
+ * ANY Not supported. You probably want ONE instead.
+ * ONE Returns the record obtained from a single replica.
+ * TWO Returns the record with the most recent timestamp once two replicas have replied.
+ * THREE Returns the record with the most recent timestamp once three replicas have replied.
+ * QUORUM Returns the record with the most recent timestamp once a majority of replicas have replied.
+ * LOCAL_QUORUM Returns the record with the most recent timestamp once a majority of replicas within the local datacenter have replied.
+ * EACH_QUORUM Returns the record with the most recent timestamp once a majority of replicas within each datacenter have replied.
+ * ALL Returns the record with the most recent timestamp once all replicas have replied (implies no replica may be down)..
+*/
+enum ConsistencyLevel {
+ ONE = 1,
+ QUORUM = 2,
+ LOCAL_QUORUM = 3,
+ EACH_QUORUM = 4,
+ ALL = 5,
+ ANY = 6,
+ TWO = 7,
+ THREE = 8,
+}
+
+/**
+ ColumnParent is used when selecting groups of columns from the same ColumnFamily. In directory structure terms, imagine
+ ColumnParent as ColumnPath + '/../'.
+
+ See also <a href="cassandra.html#Struct_ColumnPath">ColumnPath</a>
+ */
+struct ColumnParent {
+ 3: required string column_family,
+ 4: optional binary super_column,
+}
+
+/** The ColumnPath is the path to a single column in Cassandra. It might make sense to think of ColumnPath and
+ * ColumnParent in terms of a directory structure.
+ *
+ * ColumnPath is used to looking up a single column.
+ *
+ * @param column_family. The name of the CF of the column being looked up.
+ * @param super_column. The super column name.
+ * @param column. The column name.
+ */
+struct ColumnPath {
+ 3: required string column_family,
+ 4: optional binary super_column,
+ 5: optional binary column,
+}
+
+/**
+ A slice range is a structure that stores basic range, ordering and limit information for a query that will return
+ multiple columns. It could be thought of as Cassandra's version of LIMIT and ORDER BY
+
+ @param start. The column name to start the slice with. This attribute is not required, though there is no default value,
+ and can be safely set to '', i.e., an empty byte array, to start with the first column name. Otherwise, it
+ must a valid value under the rules of the Comparator defined for the given ColumnFamily.
+ @param finish. The column name to stop the slice at. This attribute is not required, though there is no default value,
+ and can be safely set to an empty byte array to not stop until 'count' results are seen. Otherwise, it
+ must also be a valid value to the ColumnFamily Comparator.
+ @param reversed. Whether the results should be ordered in reversed order. Similar to ORDER BY blah DESC in SQL.
+ @param count. How many columns to return. Similar to LIMIT in SQL. May be arbitrarily large, but Thrift will
+ materialize the whole result into memory before returning it to the client, so be aware that you may
+ be better served by iterating through slices by passing the last value of one call in as the 'start'
+ of the next instead of increasing 'count' arbitrarily large.
+ */
+struct SliceRange {
+ 1: required binary start,
+ 2: required binary finish,
+ 3: required bool reversed=0,
+ 4: required i32 count=100,
+}
+
+/**
+ A SlicePredicate is similar to a mathematic predicate (see http://en.wikipedia.org/wiki/Predicate_(mathematical_logic)),
+ which is described as "a property that the elements of a set have in common."
+
+ SlicePredicate's in Cassandra are described with either a list of column_names or a SliceRange. If column_names is
+ specified, slice_range is ignored.
+
+ @param column_name. A list of column names to retrieve. This can be used similar to Memcached's "multi-get" feature
+ to fetch N known column names. For instance, if you know you wish to fetch columns 'Joe', 'Jack',
+ and 'Jim' you can pass those column names as a list to fetch all three at once.
+ @param slice_range. A SliceRange describing how to range, order, and/or limit the slice.
+ */
+struct SlicePredicate {
+ 1: optional list<binary> column_names,
+ 2: optional SliceRange slice_range,
+}
+
+enum IndexOperator {
+ EQ,
+ GTE,
+ GT,
+ LTE,
+ LT
+}
+
+struct IndexExpression {
+ 1: required binary column_name,
+ 2: required IndexOperator op,
+ 3: required binary value,
+}
+
+struct IndexClause {
+ 1: required list<IndexExpression> expressions
+ 2: required binary start_key,
+ 3: required i32 count=100,
+}
+
+/**
+The semantics of start keys and tokens are slightly different.
+Keys are start-inclusive; tokens are start-exclusive. Token
+ranges may also wrap -- that is, the end token may be less
+than the start one. Thus, a range from keyX to keyX is a
+one-element range, but a range from tokenY to tokenY is the
+full ring.
+*/
+struct KeyRange {
+ 1: optional binary start_key,
+ 2: optional binary end_key,
+ 3: optional string start_token,
+ 4: optional string end_token,
+ 5: required i32 count=100
+}
+
+/**
+ A KeySlice is key followed by the data it maps to. A collection of KeySlice is returned by the get_range_slice operation.
+
+ @param key. a row key
+ @param columns. List of data represented by the key. Typically, the list is pared down to only the columns specified by
+ a SlicePredicate.
+ */
+struct KeySlice {
+ 1: required binary key,
+ 2: required list<ColumnOrSuperColumn> columns,
+}
+
+struct KeyCount {
+ 1: required binary key,
+ 2: required i32 count
+}
+
+/**
+ * Note that the timestamp is only optional in case of counter deletion.
+ */
+struct Deletion {
+ 1: optional i64 timestamp,
+ 2: optional binary super_column,
+ 3: optional SlicePredicate predicate,
+}
+
+/**
+ A Mutation is either an insert (represented by filling column_or_supercolumn) or a deletion (represented by filling the deletion attribute).
+ @param column_or_supercolumn. An insert to a column or supercolumn (possibly counter column or supercolumn)
+ @param deletion. A deletion of a column or supercolumn
+*/
+struct Mutation {
+ 1: optional ColumnOrSuperColumn column_or_supercolumn,
+ 2: optional Deletion deletion,
+}
+
+struct EndpointDetails {
+ 1: string host,
+ 2: string datacenter,
+ 3: optional string rack
+}
+
+/**
+ A TokenRange describes part of the Cassandra ring, it is a mapping from a range to
+ endpoints responsible for that range.
+ @param start_token The first token in the range
+ @param end_token The last token in the range
+ @param endpoints The endpoints responsible for the range (listed by their configured listen_address)
+ @param rpc_endpoints The endpoints responsible for the range (listed by their configured rpc_address)
+*/
+struct TokenRange {
+ 1: required string start_token,
+ 2: required string end_token,
+ 3: required list<string> endpoints,
+ 4: optional list<string> rpc_endpoints
+ 5: optional list<EndpointDetails> endpoint_details,
+}
+
+/**
+ Authentication requests can contain any data, dependent on the IAuthenticator used
+*/
+struct AuthenticationRequest {
+ 1: required map<string, string> credentials
+}
+
+enum IndexType {
+ KEYS,
+ CUSTOM
+}
+
+/* describes a column in a column family. */
+struct ColumnDef {
+ 1: required binary name,
+ 2: required string validation_class,
+ 3: optional IndexType index_type,
+ 4: optional string index_name,
+ 5: optional map<string,string> index_options
+}
+
+
+/* describes a column family. */
+struct CfDef {
+ 1: required string keyspace,
+ 2: required string name,
+ 3: optional string column_type="Standard",
+ 5: optional string comparator_type="BytesType",
+ 6: optional string subcomparator_type,
+ 8: optional string comment,
+ 12: optional double read_repair_chance=1.0,
+ 13: optional list<ColumnDef> column_metadata,
+ 14: optional i32 gc_grace_seconds,
+ 15: optional string default_validation_class,
+ 16: optional i32 id,
+ 17: optional i32 min_compaction_threshold,
+ 18: optional i32 max_compaction_threshold,
+ 24: optional bool replicate_on_write,
+ 25: optional double merge_shards_chance,
+ 26: optional string key_validation_class,
+ 28: optional binary key_alias,
+ 29: optional string compaction_strategy,
+ 30: optional map<string,string> compaction_strategy_options,
+ 32: optional map<string,string> compression_options,
+ 33: optional double bloom_filter_fp_chance,
+}
+
+/* describes a keyspace. */
+struct KsDef {
+ 1: required string name,
+ 2: required string strategy_class,
+ 3: optional map<string,string> strategy_options,
+
+ /** @deprecated */
+ 4: optional i32 replication_factor,
+
+ 5: required list<CfDef> cf_defs,
+ 6: optional bool durable_writes=1,
+}
+
+/** CQL query compression */
+enum Compression {
+ GZIP = 1,
+ NONE = 2
+}
+
+enum CqlResultType {
+ ROWS = 1,
+ VOID = 2,
+ INT = 3
+}
+
+/** Row returned from a CQL query */
+struct CqlRow {
+ 1: required binary key,
+ 2: required list<Column> columns
+}
+
+struct CqlMetadata {
+ 1: required map<binary,string> name_types,
+ 2: required map<binary,string> value_types,
+ 3: required string default_name_type,
+ 4: required string default_value_type
+}
+
+struct CqlResult {
+ 1: required CqlResultType type,
+ 2: optional list<CqlRow> rows,
+ 3: optional i32 num,
+ 4: optional CqlMetadata schema
+}
+
+struct CqlPreparedResult {
+ 1: required i32 itemId,
+ 2: required i32 count
+}
+
+
+service Cassandra {
+ # auth methods
+ void login(1: required AuthenticationRequest auth_request) throws (1:AuthenticationException authnx, 2:AuthorizationException authzx),
+
+ # set keyspace
+ void set_keyspace(1: required string keyspace) throws (1:InvalidRequestException ire),
+
+ # retrieval methods
+
+ /**
+ Get the Column or SuperColumn at the given column_path. If no value is present, NotFoundException is thrown. (This is
+ the only method that can throw an exception under non-failure conditions.)
+ */
+ ColumnOrSuperColumn get(1:required binary key,
+ 2:required ColumnPath column_path,
+ 3:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE)
+ throws (1:InvalidRequestException ire, 2:NotFoundException nfe, 3:UnavailableException ue, 4:TimedOutException te),
+
+ /**
+ Get the group of columns contained by column_parent (either a ColumnFamily name or a ColumnFamily/SuperColumn name
+ pair) specified by the given SlicePredicate. If no matching values are found, an empty list is returned.
+ */
+ list<ColumnOrSuperColumn> get_slice(1:required binary key,
+ 2:required ColumnParent column_parent,
+ 3:required SlicePredicate predicate,
+ 4:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE)
+ throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te),
+
+ /**
+ returns the number of columns matching <code>predicate</code> for a particular <code>key</code>,
+ <code>ColumnFamily</code> and optionally <code>SuperColumn</code>.
+ */
+ i32 get_count(1:required binary key,
+ 2:required ColumnParent column_parent,
+ 3:required SlicePredicate predicate,
+ 4:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE)
+ throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te),
+
+ /**
+ Performs a get_slice for column_parent and predicate for the given keys in parallel.
+ */
+ map<binary,list<ColumnOrSuperColumn>> multiget_slice(1:required list<binary> keys,
+ 2:required ColumnParent column_parent,
+ 3:required SlicePredicate predicate,
+ 4:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE)
+ throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te),
+
+ /**
+ Perform a get_count in parallel on the given list<binary> keys. The return value maps keys to the count found.
+ */
+ map<binary, i32> multiget_count(1:required list<binary> keys,
+ 2:required ColumnParent column_parent,
+ 3:required SlicePredicate predicate,
+ 4:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE)
+ throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te),
+
+ /**
+ returns a subset of columns for a contiguous range of keys.
+ */
+ list<KeySlice> get_range_slices(1:required ColumnParent column_parent,
+ 2:required SlicePredicate predicate,
+ 3:required KeyRange range,
+ 4:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE)
+ throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te),
+
+ /** Returns the subset of columns specified in SlicePredicate for the rows matching the IndexClause */
+ list<KeySlice> get_indexed_slices(1:required ColumnParent column_parent,
+ 2:required IndexClause index_clause,
+ 3:required SlicePredicate column_predicate,
+ 4:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE)
+ throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te),
+
+ # modification methods
+
+ /**
+ * Insert a Column at the given column_parent.column_family and optional column_parent.super_column.
+ */
+ void insert(1:required binary key,
+ 2:required ColumnParent column_parent,
+ 3:required Column column,
+ 4:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE)
+ throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te),
+
+ /**
+ * Increment or decrement a counter.
+ */
+ void add(1:required binary key,
+ 2:required ColumnParent column_parent,
+ 3:required CounterColumn column,
+ 4:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE)
+ throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te),
+
+ /**
+ Remove data from the row specified by key at the granularity specified by column_path, and the given timestamp. Note
+ that all the values in column_path besides column_path.column_family are truly optional: you can remove the entire
+ row by just specifying the ColumnFamily, or you can remove a SuperColumn or a single Column by specifying those levels too.
+ */
+ void remove(1:required binary key,
+ 2:required ColumnPath column_path,
+ 3:required i64 timestamp,
+ 4:ConsistencyLevel consistency_level=ConsistencyLevel.ONE)
+ throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te),
+
+ /**
+ * Remove a counter at the specified location.
+ * Note that counters have limited support for deletes: if you remove a counter, you must wait to issue any following update
+ * until the delete has reached all the nodes and all of them have been fully compacted.
+ */
+ void remove_counter(1:required binary key,
+ 2:required ColumnPath path,
+ 3:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE)
+ throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te),
+
+
+ /**
+ Mutate many columns or super columns for many row keys. See also: Mutation.
+
+ mutation_map maps key to column family to a list of Mutation objects to take place at that scope.
+ **/
+ void batch_mutate(1:required map<binary, map<string, list<Mutation>>> mutation_map,
+ 2:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE)
+ throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te),
+
+ /**
+ Truncate will mark and entire column family as deleted.
+ From the user's perspective a successful call to truncate will result complete data deletion from cfname.
+ Internally, however, disk space will not be immediatily released, as with all deletes in cassandra, this one
+ only marks the data as deleted.
+ The operation succeeds only if all hosts in the cluster at available and will throw an UnavailableException if
+ some hosts are down.
+ */
+ void truncate(1:required string cfname)
+ throws (1: InvalidRequestException ire, 2: UnavailableException ue, 3: TimedOutException te),
+
+
+
+ // Meta-APIs -- APIs to get information about the node or cluster,
+ // rather than user data. The nodeprobe program provides usage examples.
+
+ /**
+ * for each schema version present in the cluster, returns a list of nodes at that version.
+ * hosts that do not respond will be under the key DatabaseDescriptor.INITIAL_VERSION.
+ * the cluster is all on the same version if the size of the map is 1.
+ */
+ map<string, list<string>> describe_schema_versions()
+ throws (1: InvalidRequestException ire),
+
+ /** list the defined keyspaces in this cluster */
+ list<KsDef> describe_keyspaces()
+ throws (1:InvalidRequestException ire),
+
+ /** get the cluster name */
+ string describe_cluster_name(),
+
+ /** get the thrift api version */
+ string describe_version(),
+
+ /** get the token ring: a map of ranges to host addresses,
+ represented as a set of TokenRange instead of a map from range
+ to list of endpoints, because you can't use Thrift structs as
+ map keys:
+ https://issues.apache.org/jira/browse/THRIFT-162
+
+ for the same reason, we can't return a set here, even though
+ order is neither important nor predictable. */
+ list<TokenRange> describe_ring(1:required string keyspace)
+ throws (1:InvalidRequestException ire),
+
+ /** returns the partitioner used by this cluster */
+ string describe_partitioner(),
+
+ /** returns the snitch used by this cluster */
+ string describe_snitch(),
+
+ /** describe specified keyspace */
+ KsDef describe_keyspace(1:required string keyspace)
+ throws (1:NotFoundException nfe, 2:InvalidRequestException ire),
+
+ /** experimental API for hadoop/parallel query support.
+ may change violently and without warning.
+
+ returns list of token strings such that first subrange is (list[0], list[1]],
+ next is (list[1], list[2]], etc. */
+ list<string> describe_splits(1:required string cfName,
+ 2:required string start_token,
+ 3:required string end_token,
+ 4:required i32 keys_per_split)
+ throws (1:InvalidRequestException ire),
+
+ /** adds a column family. returns the new schema id. */
+ string system_add_column_family(1:required CfDef cf_def)
+ throws (1:InvalidRequestException ire, 2:SchemaDisagreementException sde),
+
+ /** drops a column family. returns the new schema id. */
+ string system_drop_column_family(1:required string column_family)
+ throws (1:InvalidRequestException ire, 2:SchemaDisagreementException sde),
+
+ /** adds a keyspace and any column families that are part of it. returns the new schema id. */
+ string system_add_keyspace(1:required KsDef ks_def)
+ throws (1:InvalidRequestException ire, 2:SchemaDisagreementException sde),
+
+ /** drops a keyspace and any column families that are part of it. returns the new schema id. */
+ string system_drop_keyspace(1:required string keyspace)
+ throws (1:InvalidRequestException ire, 2:SchemaDisagreementException sde),
+
+ /** updates properties of a keyspace. returns the new schema id. */
+ string system_update_keyspace(1:required KsDef ks_def)
+ throws (1:InvalidRequestException ire, 2:SchemaDisagreementException sde),
+
+ /** updates properties of a column family. returns the new schema id. */
+ string system_update_column_family(1:required CfDef cf_def)
+ throws (1:InvalidRequestException ire, 2:SchemaDisagreementException sde),
+
+ /**
+ * Executes a CQL (Cassandra Query Language) statement and returns a
+ * CqlResult containing the results.
+ */
+ CqlResult execute_cql_query(1:required binary query, 2:required Compression compression)
+ throws (1:InvalidRequestException ire,
+ 2:UnavailableException ue,
+ 3:TimedOutException te,
+ 4:SchemaDisagreementException sde)
+
+
+ /**
+ * Prepare a CQL (Cassandra Query Language) statement by compiling and returning
+ * - the type of CQL statement
+ * - an id token of the compiled CQL stored on the server side.
+ * - a count of the discovered bound markers in the statement
+ */
+ CqlPreparedResult prepare_cql_query(1:required binary query, 2:required Compression compression)
+ throws (1:InvalidRequestException ire)
+
+
+ /**
+ * Executes a prepared CQL (Cassandra Query Language) statement by passing an id token and a list of variables
+ * to bind and returns a CqlResult containing the results.
+ */
+ CqlResult execute_prepared_cql_query(1:required i32 itemId, 2:required list<string> values)
+ throws (1:InvalidRequestException ire,
+ 2:UnavailableException ue,
+ 3:TimedOutException te,
+ 4:SchemaDisagreementException sde)
+
+
+}
diff --git a/lib/netcore/Tests/Thrift.PublicInterfaces.Compile.Tests/Properties/AssemblyInfo.cs b/lib/netcore/Tests/Thrift.PublicInterfaces.Compile.Tests/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..0bb460ff4
--- /dev/null
+++ b/lib/netcore/Tests/Thrift.PublicInterfaces.Compile.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,40 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("The Apache Software Foundation")]
+[assembly: AssemblyProduct("Thrift")]
+[assembly: AssemblyCopyright("The Apache Software Foundation")]
+[assembly: AssemblyTrademark("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+
+[assembly: Guid("d0d3706b-fed5-4cf5-b984-04f448de9d7b")] \ No newline at end of file
diff --git a/lib/netcore/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.xproj b/lib/netcore/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.xproj
new file mode 100644
index 000000000..733e473e2
--- /dev/null
+++ b/lib/netcore/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.xproj
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
+ <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
+ </PropertyGroup>
+
+ <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>d0d3706b-fed5-4cf5-b984-04f448de9d7b</ProjectGuid>
+ <RootNamespace>Thrift.PublicInterfaces.Tests</RootNamespace>
+ <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
+ <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
+ <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <SchemaVersion>2.0</SchemaVersion>
+ </PropertyGroup>
+ <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
+</Project>
diff --git a/lib/netcore/Tests/Thrift.PublicInterfaces.Compile.Tests/project.json b/lib/netcore/Tests/Thrift.PublicInterfaces.Compile.Tests/project.json
new file mode 100644
index 000000000..441df7539
--- /dev/null
+++ b/lib/netcore/Tests/Thrift.PublicInterfaces.Compile.Tests/project.json
@@ -0,0 +1,25 @@
+{
+ "version": "1.0.0-*",
+
+ "dependencies": {
+ "NETStandard.Library": "1.6.0",
+ "Thrift": "1.0.0-*",
+ "System.ServiceModel.Primitives": "4.0.0"
+ },
+
+ "frameworks": {
+ "netstandard1.6": {
+ "imports": "dnxcore50"
+ }
+ },
+
+ "scripts": {
+ "precompile": [
+ /*
+ "%project:Directory%/../../thrift.exe -r -out %project:Directory%/Generated --gen netcore:wcf %project:Directory%/ThriftTestAsync.thrift",
+ "%project:Directory%/../../thrift.exe -r -out %project:Directory%/Generated --gen netcore:wcf %project:Directory%/Facebook303Test.thrift",
+ "%project:Directory%/../../thrift.exe -r -out %project:Directory%/Generated --gen netcore:wcf %project:Directory%/CassandraTest.thrift"
+ */
+ ]
+ }
+}
diff --git a/lib/netcore/Thrift.sln b/lib/netcore/Thrift.sln
new file mode 100644
index 000000000..eb6125883
--- /dev/null
+++ b/lib/netcore/Thrift.sln
@@ -0,0 +1,38 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Thrift", "Thrift\Thrift.xproj", "{6850CF46-5467-4C65-BD78-871581C539FC}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F043FC17-16B7-4497-B975-ABC12180F351}"
+ ProjectSection(SolutionItems) = preProject
+ global.json = global.json
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{F51FC4DA-CAC0-48B1-A069-B1712BCAA5BE}"
+EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Thrift.PublicInterfaces.Compile.Tests", "Tests\Thrift.PublicInterfaces.Compile.Tests\Thrift.PublicInterfaces.Compile.Tests.xproj", "{D0D3706B-FED5-4CF5-B984-04F448DE9D7B}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {6850CF46-5467-4C65-BD78-871581C539FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6850CF46-5467-4C65-BD78-871581C539FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6850CF46-5467-4C65-BD78-871581C539FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6850CF46-5467-4C65-BD78-871581C539FC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D0D3706B-FED5-4CF5-B984-04F448DE9D7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D0D3706B-FED5-4CF5-B984-04F448DE9D7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D0D3706B-FED5-4CF5-B984-04F448DE9D7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D0D3706B-FED5-4CF5-B984-04F448DE9D7B}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {D0D3706B-FED5-4CF5-B984-04F448DE9D7B} = {F51FC4DA-CAC0-48B1-A069-B1712BCAA5BE}
+ EndGlobalSection
+EndGlobal
diff --git a/lib/netcore/Thrift/Collections/TCollections.cs b/lib/netcore/Thrift/Collections/TCollections.cs
new file mode 100644
index 000000000..147bfc7d3
--- /dev/null
+++ b/lib/netcore/Thrift/Collections/TCollections.cs
@@ -0,0 +1,101 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System.Collections;
+
+namespace Thrift.Collections
+{
+ // ReSharper disable once InconsistentNaming
+ public class TCollections
+ {
+ /// <summary>
+ /// This will return true if the two collections are value-wise the same.
+ /// If the collection contains a collection, the collections will be compared using this method.
+ /// </summary>
+ public static bool Equals(IEnumerable first, IEnumerable second)
+ {
+ if (first == null && second == null)
+ {
+ return true;
+ }
+
+ if (first == null || second == null)
+ {
+ return false;
+ }
+
+ var fiter = first.GetEnumerator();
+ var siter = second.GetEnumerator();
+
+ var fnext = fiter.MoveNext();
+ var snext = siter.MoveNext();
+
+ while (fnext && snext)
+ {
+ var fenum = fiter.Current as IEnumerable;
+ var senum = siter.Current as IEnumerable;
+
+ if (fenum != null && senum != null)
+ {
+ if (!Equals(fenum, senum))
+ {
+ return false;
+ }
+ }
+ else if (fenum == null ^ senum == null)
+ {
+ return false;
+ }
+ else if (!Equals(fiter.Current, siter.Current))
+ {
+ return false;
+ }
+
+ fnext = fiter.MoveNext();
+ snext = siter.MoveNext();
+ }
+
+ return fnext == snext;
+ }
+
+ /// <summary>
+ /// This returns a hashcode based on the value of the enumerable.
+ /// </summary>
+ public static int GetHashCode(IEnumerable enumerable)
+ {
+ if (enumerable == null)
+ {
+ return 0;
+ }
+
+ var hashcode = 0;
+
+ foreach (var obj in enumerable)
+ {
+ var enum2 = obj as IEnumerable;
+ var objHash = enum2 == null ? obj.GetHashCode() : GetHashCode(enum2);
+
+ unchecked
+ {
+ hashcode = (hashcode*397) ^ (objHash);
+ }
+ }
+
+ return hashcode;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Collections/THashSet.cs b/lib/netcore/Thrift/Collections/THashSet.cs
new file mode 100644
index 000000000..011f0a0d6
--- /dev/null
+++ b/lib/netcore/Thrift/Collections/THashSet.cs
@@ -0,0 +1,67 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Thrift.Collections
+{
+ // ReSharper disable once InconsistentNaming
+ public class THashSet<T> : ICollection<T>
+ {
+ private readonly HashSet<T> _set = new HashSet<T>();
+
+ public int Count => _set.Count;
+
+ public bool IsReadOnly => false;
+
+ public void Add(T item)
+ {
+ _set.Add(item);
+ }
+
+ public void Clear()
+ {
+ _set.Clear();
+ }
+
+ public bool Contains(T item)
+ {
+ return _set.Contains(item);
+ }
+
+ public void CopyTo(T[] array, int arrayIndex)
+ {
+ _set.CopyTo(array, arrayIndex);
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return _set.GetEnumerator();
+ }
+
+ IEnumerator<T> IEnumerable<T>.GetEnumerator()
+ {
+ return ((IEnumerable<T>) _set).GetEnumerator();
+ }
+
+ public bool Remove(T item)
+ {
+ return _set.Remove(item);
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/ITAsyncProcessor.cs b/lib/netcore/Thrift/ITAsyncProcessor.cs
new file mode 100644
index 000000000..db8e40aef
--- /dev/null
+++ b/lib/netcore/Thrift/ITAsyncProcessor.cs
@@ -0,0 +1,29 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System.Threading;
+using System.Threading.Tasks;
+using Thrift.Protocols;
+
+namespace Thrift
+{
+ public interface ITAsyncProcessor
+ {
+ Task<bool> ProcessAsync(TProtocol iprot, TProtocol oprot);
+ Task<bool> ProcessAsync(TProtocol iprot, TProtocol oprot, CancellationToken cancellationToken);
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/ITProcessorFactory.cs b/lib/netcore/Thrift/ITProcessorFactory.cs
new file mode 100644
index 000000000..5133e5c48
--- /dev/null
+++ b/lib/netcore/Thrift/ITProcessorFactory.cs
@@ -0,0 +1,28 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using Thrift.Server;
+using Thrift.Transports;
+
+namespace Thrift
+{
+ // ReSharper disable once InconsistentNaming
+ public interface ITProcessorFactory
+ {
+ ITAsyncProcessor GetAsyncProcessor(TClientTransport trans, TBaseServer baseServer = null);
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Properties/AssemblyInfo.cs b/lib/netcore/Thrift/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..e3118ab23
--- /dev/null
+++ b/lib/netcore/Thrift/Properties/AssemblyInfo.cs
@@ -0,0 +1,56 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+
+[assembly: AssemblyTitle("Thrift")]
+[assembly: AssemblyDescription("C# .NET Core bindings for the Apache Thrift RPC system")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("The Apache Software Foundation")]
+[assembly: AssemblyProduct("Thrift")]
+[assembly: AssemblyCopyright("The Apache Software Foundation")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+//@TODO where to put License information?
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a exType in this assembly from
+// COM, set the ComVisible attribute to true on that exType.
+
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+
+[assembly: Guid("df3f8ef0-e0a3-4c86-a65b-8ec84e016b1d")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+
+[assembly: AssemblyVersion("1.0.0.1")]
+[assembly: AssemblyFileVersion("1.0.0.1")] \ No newline at end of file
diff --git a/lib/netcore/Thrift/Protocols/Entities/TField.cs b/lib/netcore/Thrift/Protocols/Entities/TField.cs
new file mode 100644
index 000000000..d311535e7
--- /dev/null
+++ b/lib/netcore/Thrift/Protocols/Entities/TField.cs
@@ -0,0 +1,37 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+namespace Thrift.Protocols.Entities
+{
+ // ReSharper disable once InconsistentNaming
+ public struct TField
+ {
+ public TField(string name, TType type, short id)
+ {
+ Name = name;
+ Type = type;
+ ID = id;
+ }
+
+ public string Name { get; set; }
+
+ public TType Type { get; set; }
+
+ // ReSharper disable once InconsistentNaming - do not rename - it used for generation
+ public short ID { get; set; }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Protocols/Entities/TList.cs b/lib/netcore/Thrift/Protocols/Entities/TList.cs
new file mode 100644
index 000000000..ce232207c
--- /dev/null
+++ b/lib/netcore/Thrift/Protocols/Entities/TList.cs
@@ -0,0 +1,33 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+namespace Thrift.Protocols.Entities
+{
+ // ReSharper disable once InconsistentNaming
+ public struct TList
+ {
+ public TList(TType elementType, int count)
+ {
+ ElementType = elementType;
+ Count = count;
+ }
+
+ public TType ElementType { get; set; }
+
+ public int Count { get; set; }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Protocols/Entities/TMap.cs b/lib/netcore/Thrift/Protocols/Entities/TMap.cs
new file mode 100644
index 000000000..9195593db
--- /dev/null
+++ b/lib/netcore/Thrift/Protocols/Entities/TMap.cs
@@ -0,0 +1,36 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+namespace Thrift.Protocols.Entities
+{
+ // ReSharper disable once InconsistentNaming
+ public struct TMap
+ {
+ public TMap(TType keyType, TType valueType, int count)
+ {
+ KeyType = keyType;
+ ValueType = valueType;
+ Count = count;
+ }
+
+ public TType KeyType { get; set; }
+
+ public TType ValueType { get; set; }
+
+ public int Count { get; set; }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Protocols/Entities/TMessage.cs b/lib/netcore/Thrift/Protocols/Entities/TMessage.cs
new file mode 100644
index 000000000..17f49298f
--- /dev/null
+++ b/lib/netcore/Thrift/Protocols/Entities/TMessage.cs
@@ -0,0 +1,37 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+namespace Thrift.Protocols.Entities
+{
+ // ReSharper disable once InconsistentNaming
+ public struct TMessage
+ {
+ public TMessage(string name, TMessageType type, int seqid)
+ {
+ Name = name;
+ Type = type;
+ SeqID = seqid;
+ }
+
+ public string Name { get; set; }
+
+ public TMessageType Type { get; set; }
+
+ // ReSharper disable once InconsistentNaming - do not rename - it used for generation
+ public int SeqID { get; set; }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Protocols/Entities/TMessageType.cs b/lib/netcore/Thrift/Protocols/Entities/TMessageType.cs
new file mode 100644
index 000000000..d7b9a2275
--- /dev/null
+++ b/lib/netcore/Thrift/Protocols/Entities/TMessageType.cs
@@ -0,0 +1,28 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+namespace Thrift.Protocols.Entities
+{
+ // ReSharper disable once InconsistentNaming
+ public enum TMessageType
+ {
+ Call = 1,
+ Reply = 2,
+ Exception = 3,
+ Oneway = 4
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Protocols/Entities/TSet.cs b/lib/netcore/Thrift/Protocols/Entities/TSet.cs
new file mode 100644
index 000000000..a583b54a6
--- /dev/null
+++ b/lib/netcore/Thrift/Protocols/Entities/TSet.cs
@@ -0,0 +1,38 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+namespace Thrift.Protocols.Entities
+{
+ // ReSharper disable once InconsistentNaming
+ public struct TSet
+ {
+ public TSet(TType elementType, int count)
+ {
+ ElementType = elementType;
+ Count = count;
+ }
+
+ public TSet(TList list)
+ : this(list.ElementType, list.Count)
+ {
+ }
+
+ public TType ElementType { get; set; }
+
+ public int Count { get; set; }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Protocols/Entities/TStruct.cs b/lib/netcore/Thrift/Protocols/Entities/TStruct.cs
new file mode 100644
index 000000000..a28dcc3d0
--- /dev/null
+++ b/lib/netcore/Thrift/Protocols/Entities/TStruct.cs
@@ -0,0 +1,30 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+namespace Thrift.Protocols.Entities
+{
+ // ReSharper disable once InconsistentNaming
+ public struct TStruct
+ {
+ public TStruct(string name)
+ {
+ Name = name;
+ }
+
+ public string Name { get; set; }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Protocols/Entities/TType.cs b/lib/netcore/Thrift/Protocols/Entities/TType.cs
new file mode 100644
index 000000000..ebe781c95
--- /dev/null
+++ b/lib/netcore/Thrift/Protocols/Entities/TType.cs
@@ -0,0 +1,37 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+namespace Thrift.Protocols.Entities
+{
+ // ReSharper disable once InconsistentNaming
+ public enum TType : byte
+ {
+ Stop = 0,
+ Void = 1,
+ Bool = 2,
+ Byte = 3,
+ Double = 4,
+ I16 = 6,
+ I32 = 8,
+ I64 = 10,
+ String = 11,
+ Struct = 12,
+ Map = 13,
+ Set = 14,
+ List = 15
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Protocols/ITProtocolFactory.cs b/lib/netcore/Thrift/Protocols/ITProtocolFactory.cs
new file mode 100644
index 000000000..ecc5cc494
--- /dev/null
+++ b/lib/netcore/Thrift/Protocols/ITProtocolFactory.cs
@@ -0,0 +1,27 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using Thrift.Transports;
+
+namespace Thrift.Protocols
+{
+ // ReSharper disable once InconsistentNaming
+ public interface ITProtocolFactory
+ {
+ TProtocol GetProtocol(TClientTransport trans);
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Protocols/TAbstractBase.cs b/lib/netcore/Thrift/Protocols/TAbstractBase.cs
new file mode 100644
index 000000000..eddb85e75
--- /dev/null
+++ b/lib/netcore/Thrift/Protocols/TAbstractBase.cs
@@ -0,0 +1,28 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Thrift.Protocols
+{
+ // ReSharper disable once InconsistentNaming
+ public interface TAbstractBase
+ {
+ Task WriteAsync(TProtocol tProtocol, CancellationToken cancellationToken);
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Protocols/TBase.cs b/lib/netcore/Thrift/Protocols/TBase.cs
new file mode 100644
index 000000000..cd1109971
--- /dev/null
+++ b/lib/netcore/Thrift/Protocols/TBase.cs
@@ -0,0 +1,28 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Thrift.Protocols
+{
+ // ReSharper disable once InconsistentNaming
+ public interface TBase : TAbstractBase
+ {
+ Task ReadAsync(TProtocol tProtocol, CancellationToken cancellationToken);
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Protocols/TBinaryProtocol.cs b/lib/netcore/Thrift/Protocols/TBinaryProtocol.cs
new file mode 100644
index 000000000..fa0c5fc93
--- /dev/null
+++ b/lib/netcore/Thrift/Protocols/TBinaryProtocol.cs
@@ -0,0 +1,608 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Thrift.Protocols.Entities;
+using Thrift.Transports;
+
+namespace Thrift.Protocols
+{
+ // ReSharper disable once InconsistentNaming
+ public class TBinaryProtocol : TProtocol
+ {
+ //TODO: Unit tests
+ //TODO: Localization
+ //TODO: pragma
+
+ protected const uint VersionMask = 0xffff0000;
+ protected const uint Version1 = 0x80010000;
+
+ protected bool StrictRead;
+ protected bool StrictWrite;
+
+ public TBinaryProtocol(TClientTransport trans)
+ : this(trans, false, true)
+ {
+ }
+
+ public TBinaryProtocol(TClientTransport trans, bool strictRead, bool strictWrite)
+ : base(trans)
+ {
+ StrictRead = strictRead;
+ StrictWrite = strictWrite;
+ }
+
+ public override async Task WriteMessageBeginAsync(TMessage message, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ if (StrictWrite)
+ {
+ var version = Version1 | (uint) message.Type;
+ await WriteI32Async((int) version, cancellationToken);
+ await WriteStringAsync(message.Name, cancellationToken);
+ await WriteI32Async(message.SeqID, cancellationToken);
+ }
+ else
+ {
+ await WriteStringAsync(message.Name, cancellationToken);
+ await WriteByteAsync((sbyte) message.Type, cancellationToken);
+ await WriteI32Async(message.SeqID, cancellationToken);
+ }
+ }
+
+ public override async Task WriteMessageEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task WriteStructBeginAsync(TStruct struc, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task WriteStructEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task WriteFieldBeginAsync(TField field, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await WriteByteAsync((sbyte) field.Type, cancellationToken);
+ await WriteI16Async(field.ID, cancellationToken);
+ }
+
+ public override async Task WriteFieldEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task WriteFieldStopAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await WriteByteAsync((sbyte) TType.Stop, cancellationToken);
+ }
+
+ public override async Task WriteMapBeginAsync(TMap map, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await WriteByteAsync((sbyte) map.KeyType, cancellationToken);
+ await WriteByteAsync((sbyte) map.ValueType, cancellationToken);
+ await WriteI32Async(map.Count, cancellationToken);
+ }
+
+ public override async Task WriteMapEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task WriteListBeginAsync(TList list, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await WriteByteAsync((sbyte) list.ElementType, cancellationToken);
+ await WriteI32Async(list.Count, cancellationToken);
+ }
+
+ public override async Task WriteListEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task WriteSetBeginAsync(TSet set, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await WriteByteAsync((sbyte) set.ElementType, cancellationToken);
+ await WriteI32Async(set.Count, cancellationToken);
+ }
+
+ public override async Task WriteSetEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task WriteBoolAsync(bool b, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await WriteByteAsync(b ? (sbyte) 1 : (sbyte) 0, cancellationToken);
+ }
+
+ protected internal static byte[] CreateWriteByte(sbyte b)
+ {
+ var bout = new byte[1];
+
+ bout[0] = (byte) b;
+
+ return bout;
+ }
+
+ public override async Task WriteByteAsync(sbyte b, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ var bout = CreateWriteByte(b);
+ await Trans.WriteAsync(bout, 0, 1, cancellationToken);
+ }
+
+ protected internal static byte[] CreateWriteI16(short s)
+ {
+ var i16Out = new byte[2];
+
+ i16Out[0] = (byte) (0xff & (s >> 8));
+ i16Out[1] = (byte) (0xff & s);
+
+ return i16Out;
+ }
+
+ public override async Task WriteI16Async(short i16, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ var i16Out = CreateWriteI16(i16);
+ await Trans.WriteAsync(i16Out, 0, 2, cancellationToken);
+ }
+
+ protected internal static byte[] CreateWriteI32(int i32)
+ {
+ var i32Out = new byte[4];
+
+ i32Out[0] = (byte) (0xff & (i32 >> 24));
+ i32Out[1] = (byte) (0xff & (i32 >> 16));
+ i32Out[2] = (byte) (0xff & (i32 >> 8));
+ i32Out[3] = (byte) (0xff & i32);
+
+ return i32Out;
+ }
+
+ public override async Task WriteI32Async(int i32, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ var i32Out = CreateWriteI32(i32);
+ await Trans.WriteAsync(i32Out, 0, 4, cancellationToken);
+ }
+
+ protected internal static byte[] CreateWriteI64(long i64)
+ {
+ var i64Out = new byte[8];
+
+ i64Out[0] = (byte) (0xff & (i64 >> 56));
+ i64Out[1] = (byte) (0xff & (i64 >> 48));
+ i64Out[2] = (byte) (0xff & (i64 >> 40));
+ i64Out[3] = (byte) (0xff & (i64 >> 32));
+ i64Out[4] = (byte) (0xff & (i64 >> 24));
+ i64Out[5] = (byte) (0xff & (i64 >> 16));
+ i64Out[6] = (byte) (0xff & (i64 >> 8));
+ i64Out[7] = (byte) (0xff & i64);
+
+ return i64Out;
+ }
+
+ public override async Task WriteI64Async(long i64, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ var i64Out = CreateWriteI64(i64);
+ await Trans.WriteAsync(i64Out, 0, 8, cancellationToken);
+ }
+
+ public override async Task WriteDoubleAsync(double d, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await WriteI64Async(BitConverter.DoubleToInt64Bits(d), cancellationToken);
+ }
+
+ public override async Task WriteBinaryAsync(byte[] b, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await WriteI32Async(b.Length, cancellationToken);
+ await Trans.WriteAsync(b, 0, b.Length, cancellationToken);
+ }
+
+ public override async Task<TMessage> ReadMessageBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<TMessage>(cancellationToken);
+ }
+
+ var message = new TMessage();
+ var size = await ReadI32Async(cancellationToken);
+ if (size < 0)
+ {
+ var version = (uint) size & VersionMask;
+ if (version != Version1)
+ {
+ throw new TProtocolException(TProtocolException.BAD_VERSION,
+ $"Bad version in ReadMessageBegin: {version}");
+ }
+ message.Type = (TMessageType) (size & 0x000000ff);
+ message.Name = await ReadStringAsync(cancellationToken);
+ message.SeqID = await ReadI32Async(cancellationToken);
+ }
+ else
+ {
+ if (StrictRead)
+ {
+ throw new TProtocolException(TProtocolException.BAD_VERSION,
+ "Missing version in ReadMessageBegin, old client?");
+ }
+ message.Name = await ReadStringBodyAsync(size, cancellationToken);
+ message.Type = (TMessageType) await ReadByteAsync(cancellationToken);
+ message.SeqID = await ReadI32Async(cancellationToken);
+ }
+ return message;
+ }
+
+ public override async Task ReadMessageEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task<TStruct> ReadStructBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+
+ //TODO: no read from internal transport?
+ return new TStruct();
+ }
+
+ public override async Task ReadStructEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task<TField> ReadFieldBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<TField>(cancellationToken);
+ }
+
+ var field = new TField
+ {
+ Type = (TType) await ReadByteAsync(cancellationToken)
+ };
+
+ if (field.Type != TType.Stop)
+ {
+ field.ID = await ReadI16Async(cancellationToken);
+ }
+
+ return field;
+ }
+
+ public override async Task ReadFieldEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task<TMap> ReadMapBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<TMap>(cancellationToken);
+ }
+
+ var map = new TMap
+ {
+ KeyType = (TType) await ReadByteAsync(cancellationToken),
+ ValueType = (TType) await ReadByteAsync(cancellationToken),
+ Count = await ReadI32Async(cancellationToken)
+ };
+
+ return map;
+ }
+
+ public override async Task ReadMapEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task<TList> ReadListBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<TList>(cancellationToken);
+ }
+
+ var list = new TList
+ {
+ ElementType = (TType) await ReadByteAsync(cancellationToken),
+ Count = await ReadI32Async(cancellationToken)
+ };
+
+ return list;
+ }
+
+ public override async Task ReadListEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task<TSet> ReadSetBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<TSet>(cancellationToken);
+ }
+
+ var set = new TSet
+ {
+ ElementType = (TType) await ReadByteAsync(cancellationToken),
+ Count = await ReadI32Async(cancellationToken)
+ };
+
+ return set;
+ }
+
+ public override async Task ReadSetEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task<bool> ReadBoolAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<bool>(cancellationToken);
+ }
+
+ return await ReadByteAsync(cancellationToken) == 1;
+ }
+
+ public override async Task<sbyte> ReadByteAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<sbyte>(cancellationToken);
+ }
+
+ var bin = new byte[1];
+ await Trans.ReadAllAsync(bin, 0, 1, cancellationToken); //TODO: why readall ?
+ return (sbyte) bin[0];
+ }
+
+ public override async Task<short> ReadI16Async(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<short>(cancellationToken);
+ }
+
+ var i16In = new byte[2];
+ await Trans.ReadAllAsync(i16In, 0, 2, cancellationToken);
+ var result = (short) (((i16In[0] & 0xff) << 8) | i16In[1] & 0xff);
+ return result;
+ }
+
+ public override async Task<int> ReadI32Async(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<int>(cancellationToken);
+ }
+
+ var i32In = new byte[4];
+ await Trans.ReadAllAsync(i32In, 0, 4, cancellationToken);
+ var result = ((i32In[0] & 0xff) << 24) | ((i32In[1] & 0xff) << 16) | ((i32In[2] & 0xff) << 8) |
+ i32In[3] & 0xff;
+ return result;
+ }
+
+#pragma warning disable 675
+
+ protected internal long CreateReadI64(byte[] buf)
+ {
+ var result =
+ ((long) (buf[0] & 0xff) << 56) |
+ ((long) (buf[1] & 0xff) << 48) |
+ ((long) (buf[2] & 0xff) << 40) |
+ ((long) (buf[3] & 0xff) << 32) |
+ ((long) (buf[4] & 0xff) << 24) |
+ ((long) (buf[5] & 0xff) << 16) |
+ ((long) (buf[6] & 0xff) << 8) |
+ buf[7] & 0xff;
+
+ return result;
+ }
+
+#pragma warning restore 675
+
+ public override async Task<long> ReadI64Async(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<long>(cancellationToken);
+ }
+
+ var i64In = new byte[8];
+ await Trans.ReadAllAsync(i64In, 0, 8, cancellationToken);
+ return CreateReadI64(i64In);
+ }
+
+ public override async Task<double> ReadDoubleAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<double>(cancellationToken);
+ }
+
+ var d = await ReadI64Async(cancellationToken);
+ return BitConverter.Int64BitsToDouble(d);
+ }
+
+ public override async Task<byte[]> ReadBinaryAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<byte[]>(cancellationToken);
+ }
+
+ var size = await ReadI32Async(cancellationToken);
+ var buf = new byte[size];
+ await Trans.ReadAllAsync(buf, 0, size, cancellationToken);
+ return buf;
+ }
+
+ private async Task<string> ReadStringBodyAsync(int size, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled<string>(cancellationToken);
+ }
+
+ var buf = new byte[size];
+ await Trans.ReadAllAsync(buf, 0, size, cancellationToken);
+ return Encoding.UTF8.GetString(buf, 0, buf.Length);
+ }
+
+ public class Factory : ITProtocolFactory
+ {
+ protected bool StrictRead;
+ protected bool StrictWrite;
+
+ public Factory()
+ : this(false, true)
+ {
+ }
+
+ public Factory(bool strictRead, bool strictWrite)
+ {
+ StrictRead = strictRead;
+ StrictWrite = strictWrite;
+ }
+
+ public TProtocol GetProtocol(TClientTransport trans)
+ {
+ return new TBinaryProtocol(trans, StrictRead, StrictWrite);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Protocols/TCompactProtocol.cs b/lib/netcore/Thrift/Protocols/TCompactProtocol.cs
new file mode 100644
index 000000000..6d5e0bf35
--- /dev/null
+++ b/lib/netcore/Thrift/Protocols/TCompactProtocol.cs
@@ -0,0 +1,922 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Thrift.Protocols.Entities;
+using Thrift.Transports;
+
+namespace Thrift.Protocols
+{
+ //TODO: implementation of TProtocol
+
+ // ReSharper disable once InconsistentNaming
+ public class TCompactProtocol : TProtocol
+ {
+ private const byte ProtocolId = 0x82;
+ private const byte Version = 1;
+ private const byte VersionMask = 0x1f; // 0001 1111
+ private const byte TypeMask = 0xE0; // 1110 0000
+ private const byte TypeBits = 0x07; // 0000 0111
+ private const int TypeShiftAmount = 5;
+ private static readonly TStruct AnonymousStruct = new TStruct(string.Empty);
+ private static readonly TField Tstop = new TField(string.Empty, TType.Stop, 0);
+
+ // ReSharper disable once InconsistentNaming
+ private static readonly byte[] TTypeToCompactType = new byte[16];
+
+ /// <summary>
+ /// Used to keep track of the last field for the current and previous structs, so we can do the delta stuff.
+ /// </summary>
+ private readonly Stack<short> _lastField = new Stack<short>(15);
+
+ /// <summary>
+ /// If we encounter a boolean field begin, save the TField here so it can have the value incorporated.
+ /// </summary>
+ private TField? _booleanField;
+
+ /// <summary>
+ /// If we Read a field header, and it's a boolean field, save the boolean value here so that ReadBool can use it.
+ /// </summary>
+ private bool? _boolValue;
+
+ private short _lastFieldId;
+
+ public TCompactProtocol(TClientTransport trans)
+ : base(trans)
+ {
+ TTypeToCompactType[(int) TType.Stop] = Types.Stop;
+ TTypeToCompactType[(int) TType.Bool] = Types.BooleanTrue;
+ TTypeToCompactType[(int) TType.Byte] = Types.Byte;
+ TTypeToCompactType[(int) TType.I16] = Types.I16;
+ TTypeToCompactType[(int) TType.I32] = Types.I32;
+ TTypeToCompactType[(int) TType.I64] = Types.I64;
+ TTypeToCompactType[(int) TType.Double] = Types.Double;
+ TTypeToCompactType[(int) TType.String] = Types.Binary;
+ TTypeToCompactType[(int) TType.List] = Types.List;
+ TTypeToCompactType[(int) TType.Set] = Types.Set;
+ TTypeToCompactType[(int) TType.Map] = Types.Map;
+ TTypeToCompactType[(int) TType.Struct] = Types.Struct;
+ }
+
+ public void Reset()
+ {
+ _lastField.Clear();
+ _lastFieldId = 0;
+ }
+
+ public override async Task WriteMessageBeginAsync(TMessage message, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await Trans.WriteAsync(new[] {ProtocolId}, cancellationToken);
+ await
+ Trans.WriteAsync(
+ new[] {(byte) ((Version & VersionMask) | (((uint) message.Type << TypeShiftAmount) & TypeMask))},
+ cancellationToken);
+
+ var bufferTuple = CreateWriteVarInt32((uint) message.SeqID);
+ await Trans.WriteAsync(bufferTuple.Item1, 0, bufferTuple.Item2, cancellationToken);
+
+ await WriteStringAsync(message.Name, cancellationToken);
+ }
+
+ public override async Task WriteMessageEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ /// <summary>
+ /// Write a struct begin. This doesn't actually put anything on the wire. We
+ /// use it as an opportunity to put special placeholder markers on the field
+ /// stack so we can get the field id deltas correct.
+ /// </summary>
+ public override async Task WriteStructBeginAsync(TStruct struc, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+
+ _lastField.Push(_lastFieldId);
+ _lastFieldId = 0;
+ }
+
+ public override async Task WriteStructEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+
+ _lastFieldId = _lastField.Pop();
+ }
+
+ private async Task WriteFieldBeginInternalAsync(TField field, byte typeOverride,
+ CancellationToken cancellationToken)
+ {
+ // if there's a exType override, use that.
+ var typeToWrite = typeOverride == 0xFF ? GetCompactType(field.Type) : typeOverride;
+
+ // check if we can use delta encoding for the field id
+ if ((field.ID > _lastFieldId) && (field.ID - _lastFieldId <= 15))
+ {
+ var b = (byte) (((field.ID - _lastFieldId) << 4) | typeToWrite);
+ // Write them together
+ await Trans.WriteAsync(new[] {b}, cancellationToken);
+ }
+ else
+ {
+ // Write them separate
+ await Trans.WriteAsync(new[] {typeToWrite}, cancellationToken);
+ await WriteI16Async(field.ID, cancellationToken);
+ }
+
+ _lastFieldId = field.ID;
+ }
+
+ public override async Task WriteFieldBeginAsync(TField field, CancellationToken cancellationToken)
+ {
+ if (field.Type == TType.Bool)
+ {
+ _booleanField = field;
+ }
+ else
+ {
+ await WriteFieldBeginInternalAsync(field, 0xFF, cancellationToken);
+ }
+ }
+
+ public override async Task WriteFieldEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task WriteFieldStopAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await Trans.WriteAsync(new[] {Types.Stop}, cancellationToken);
+ }
+
+ protected async Task WriteCollectionBeginAsync(TType elemType, int size, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ /*
+ Abstract method for writing the start of lists and sets. List and sets on
+ the wire differ only by the exType indicator.
+ */
+
+ if (size <= 14)
+ {
+ await Trans.WriteAsync(new[] {(byte) ((size << 4) | GetCompactType(elemType))}, cancellationToken);
+ }
+ else
+ {
+ await Trans.WriteAsync(new[] {(byte) (0xf0 | GetCompactType(elemType))}, cancellationToken);
+
+ var bufferTuple = CreateWriteVarInt32((uint) size);
+ await Trans.WriteAsync(bufferTuple.Item1, 0, bufferTuple.Item2, cancellationToken);
+ }
+ }
+
+ public override async Task WriteListBeginAsync(TList list, CancellationToken cancellationToken)
+ {
+ await WriteCollectionBeginAsync(list.ElementType, list.Count, cancellationToken);
+ }
+
+ public override async Task WriteListEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task WriteSetBeginAsync(TSet set, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await WriteCollectionBeginAsync(set.ElementType, set.Count, cancellationToken);
+ }
+
+ public override async Task WriteSetEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task WriteBoolAsync(bool b, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ /*
+ Write a boolean value. Potentially, this could be a boolean field, in
+ which case the field header info isn't written yet. If so, decide what the
+ right exType header is for the value and then Write the field header.
+ Otherwise, Write a single byte.
+ */
+
+ if (_booleanField != null)
+ {
+ // we haven't written the field header yet
+ await
+ WriteFieldBeginInternalAsync(_booleanField.Value, b ? Types.BooleanTrue : Types.BooleanFalse,
+ cancellationToken);
+ _booleanField = null;
+ }
+ else
+ {
+ // we're not part of a field, so just Write the value.
+ await Trans.WriteAsync(new[] {b ? Types.BooleanTrue : Types.BooleanFalse}, cancellationToken);
+ }
+ }
+
+ public override async Task WriteByteAsync(sbyte b, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await Trans.WriteAsync(new[] {(byte) b}, cancellationToken);
+ }
+
+ public override async Task WriteI16Async(short i16, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ var bufferTuple = CreateWriteVarInt32(IntToZigzag(i16));
+ await Trans.WriteAsync(bufferTuple.Item1, 0, bufferTuple.Item2, cancellationToken);
+ }
+
+ protected internal Tuple<byte[], int> CreateWriteVarInt32(uint n)
+ {
+ // Write an i32 as a varint.Results in 1 - 5 bytes on the wire.
+ var i32Buf = new byte[5];
+ var idx = 0;
+
+ while (true)
+ {
+ if ((n & ~0x7F) == 0)
+ {
+ i32Buf[idx++] = (byte) n;
+ break;
+ }
+
+ i32Buf[idx++] = (byte) ((n & 0x7F) | 0x80);
+ n >>= 7;
+ }
+
+ return new Tuple<byte[], int>(i32Buf, idx);
+ }
+
+ public override async Task WriteI32Async(int i32, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ var bufferTuple = CreateWriteVarInt32(IntToZigzag(i32));
+ await Trans.WriteAsync(bufferTuple.Item1, 0, bufferTuple.Item2, cancellationToken);
+ }
+
+ protected internal Tuple<byte[], int> CreateWriteVarInt64(ulong n)
+ {
+ // Write an i64 as a varint. Results in 1-10 bytes on the wire.
+ var buf = new byte[10];
+ var idx = 0;
+
+ while (true)
+ {
+ if ((n & ~(ulong) 0x7FL) == 0)
+ {
+ buf[idx++] = (byte) n;
+ break;
+ }
+ buf[idx++] = (byte) ((n & 0x7F) | 0x80);
+ n >>= 7;
+ }
+
+ return new Tuple<byte[], int>(buf, idx);
+ }
+
+ public override async Task WriteI64Async(long i64, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ var bufferTuple = CreateWriteVarInt64(LongToZigzag(i64));
+ await Trans.WriteAsync(bufferTuple.Item1, 0, bufferTuple.Item2, cancellationToken);
+ }
+
+ public override async Task WriteDoubleAsync(double d, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ var data = new byte[8];
+ FixedLongToBytes(BitConverter.DoubleToInt64Bits(d), data, 0);
+ await Trans.WriteAsync(data, cancellationToken);
+ }
+
+ public override async Task WriteStringAsync(string str, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ var bytes = Encoding.UTF8.GetBytes(str);
+
+ var bufferTuple = CreateWriteVarInt32((uint) bytes.Length);
+ await Trans.WriteAsync(bufferTuple.Item1, 0, bufferTuple.Item2, cancellationToken);
+ await Trans.WriteAsync(bytes, 0, bytes.Length, cancellationToken);
+ }
+
+ public override async Task WriteBinaryAsync(byte[] b, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ var bufferTuple = CreateWriteVarInt32((uint) b.Length);
+ await Trans.WriteAsync(bufferTuple.Item1, 0, bufferTuple.Item2, cancellationToken);
+ await Trans.WriteAsync(b, 0, b.Length, cancellationToken);
+ }
+
+ public override async Task WriteMapBeginAsync(TMap map, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ if (map.Count == 0)
+ {
+ await Trans.WriteAsync(new[] {(byte) 0}, cancellationToken);
+ }
+ else
+ {
+ var bufferTuple = CreateWriteVarInt32((uint) map.Count);
+ await Trans.WriteAsync(bufferTuple.Item1, 0, bufferTuple.Item2, cancellationToken);
+ await
+ Trans.WriteAsync(
+ new[] {(byte) ((GetCompactType(map.KeyType) << 4) | GetCompactType(map.ValueType))},
+ cancellationToken);
+ }
+ }
+
+ public override async Task WriteMapEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task<TMessage> ReadMessageBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<TMessage>(cancellationToken);
+ }
+
+ var protocolId = (byte) await ReadByteAsync(cancellationToken);
+ if (protocolId != ProtocolId)
+ {
+ throw new TProtocolException($"Expected protocol id {ProtocolId:X} but got {protocolId:X}");
+ }
+
+ var versionAndType = (byte) await ReadByteAsync(cancellationToken);
+ var version = (byte) (versionAndType & VersionMask);
+
+ if (version != Version)
+ {
+ throw new TProtocolException($"Expected version {Version} but got {version}");
+ }
+
+ var type = (byte) ((versionAndType >> TypeShiftAmount) & TypeBits);
+ var seqid = (int) await ReadVarInt32Async(cancellationToken);
+ var messageName = await ReadStringAsync(cancellationToken);
+
+ return new TMessage(messageName, (TMessageType) type, seqid);
+ }
+
+ public override async Task ReadMessageEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task<TStruct> ReadStructBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<TStruct>(cancellationToken);
+ }
+
+ // some magic is here )
+
+ _lastField.Push(_lastFieldId);
+ _lastFieldId = 0;
+
+ return AnonymousStruct;
+ }
+
+ public override async Task ReadStructEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+
+ /*
+ Doesn't actually consume any wire data, just removes the last field for
+ this struct from the field stack.
+ */
+
+ // consume the last field we Read off the wire.
+ _lastFieldId = _lastField.Pop();
+ }
+
+ public override async Task<TField> ReadFieldBeginAsync(CancellationToken cancellationToken)
+ {
+ // Read a field header off the wire.
+ var type = (byte) await ReadByteAsync(cancellationToken);
+ // if it's a stop, then we can return immediately, as the struct is over.
+ if (type == Types.Stop)
+ {
+ return Tstop;
+ }
+
+ short fieldId;
+ // mask off the 4 MSB of the exType header. it could contain a field id delta.
+ var modifier = (short) ((type & 0xf0) >> 4);
+ if (modifier == 0)
+ {
+ fieldId = await ReadI16Async(cancellationToken);
+ }
+ else
+ {
+ fieldId = (short) (_lastFieldId + modifier);
+ }
+
+ var field = new TField(string.Empty, GetTType((byte) (type & 0x0f)), fieldId);
+ // if this happens to be a boolean field, the value is encoded in the exType
+ if (IsBoolType(type))
+ {
+ _boolValue = (byte) (type & 0x0f) == Types.BooleanTrue;
+ }
+
+ // push the new field onto the field stack so we can keep the deltas going.
+ _lastFieldId = field.ID;
+ return field;
+ }
+
+ public override async Task ReadFieldEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task<TMap> ReadMapBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled<TMap>(cancellationToken);
+ }
+
+ /*
+ Read a map header off the wire. If the size is zero, skip Reading the key
+ and value exType. This means that 0-length maps will yield TMaps without the
+ "correct" types.
+ */
+
+ var size = (int) await ReadVarInt32Async(cancellationToken);
+ var keyAndValueType = size == 0 ? (byte) 0 : (byte) await ReadByteAsync(cancellationToken);
+ return new TMap(GetTType((byte) (keyAndValueType >> 4)), GetTType((byte) (keyAndValueType & 0xf)), size);
+ }
+
+ public override async Task ReadMapEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task<TSet> ReadSetBeginAsync(CancellationToken cancellationToken)
+ {
+ /*
+ Read a set header off the wire. If the set size is 0-14, the size will
+ be packed into the element exType header. If it's a longer set, the 4 MSB
+ of the element exType header will be 0xF, and a varint will follow with the
+ true size.
+ */
+
+ return new TSet(await ReadListBeginAsync(cancellationToken));
+ }
+
+ public override async Task<bool> ReadBoolAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<bool>(cancellationToken);
+ }
+
+ /*
+ Read a boolean off the wire. If this is a boolean field, the value should
+ already have been Read during ReadFieldBegin, so we'll just consume the
+ pre-stored value. Otherwise, Read a byte.
+ */
+
+ if (_boolValue != null)
+ {
+ var result = _boolValue.Value;
+ _boolValue = null;
+ return result;
+ }
+
+ return await ReadByteAsync(cancellationToken) == Types.BooleanTrue;
+ }
+
+ public override async Task<sbyte> ReadByteAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<sbyte>(cancellationToken);
+ }
+
+ // Read a single byte off the wire. Nothing interesting here.
+ var buf = new byte[1];
+ await Trans.ReadAllAsync(buf, 0, 1, cancellationToken);
+ return (sbyte) buf[0];
+ }
+
+ public override async Task<short> ReadI16Async(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<short>(cancellationToken);
+ }
+
+ return (short) ZigzagToInt(await ReadVarInt32Async(cancellationToken));
+ }
+
+ public override async Task<int> ReadI32Async(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<int>(cancellationToken);
+ }
+
+ return ZigzagToInt(await ReadVarInt32Async(cancellationToken));
+ }
+
+ public override async Task<long> ReadI64Async(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<long>(cancellationToken);
+ }
+
+ return ZigzagToLong(await ReadVarInt64Async(cancellationToken));
+ }
+
+ public override async Task<double> ReadDoubleAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<double>(cancellationToken);
+ }
+
+ var longBits = new byte[8];
+ await Trans.ReadAllAsync(longBits, 0, 8, cancellationToken);
+
+ return BitConverter.Int64BitsToDouble(BytesToLong(longBits));
+ }
+
+ public override async Task<string> ReadStringAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled<string>(cancellationToken);
+ }
+
+ // Reads a byte[] (via ReadBinary), and then UTF-8 decodes it.
+ var length = (int) await ReadVarInt32Async(cancellationToken);
+
+ if (length == 0)
+ {
+ return string.Empty;
+ }
+
+ var buf = new byte[length];
+ await Trans.ReadAllAsync(buf, 0, length, cancellationToken);
+
+ return Encoding.UTF8.GetString(buf);
+ }
+
+ public override async Task<byte[]> ReadBinaryAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<byte[]>(cancellationToken);
+ }
+
+ // Read a byte[] from the wire.
+ var length = (int) await ReadVarInt32Async(cancellationToken);
+ if (length == 0)
+ {
+ return new byte[0];
+ }
+
+ var buf = new byte[length];
+ await Trans.ReadAllAsync(buf, 0, length, cancellationToken);
+ return buf;
+ }
+
+ public override async Task<TList> ReadListBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled<TList>(cancellationToken);
+ }
+
+ /*
+ Read a list header off the wire. If the list size is 0-14, the size will
+ be packed into the element exType header. If it's a longer list, the 4 MSB
+ of the element exType header will be 0xF, and a varint will follow with the
+ true size.
+ */
+
+ var sizeAndType = (byte) await ReadByteAsync(cancellationToken);
+ var size = (sizeAndType >> 4) & 0x0f;
+ if (size == 15)
+ {
+ size = (int) await ReadVarInt32Async(cancellationToken);
+ }
+
+ var type = GetTType(sizeAndType);
+ return new TList(type, size);
+ }
+
+ public override async Task ReadListEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task ReadSetEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ private static byte GetCompactType(TType ttype)
+ {
+ // Given a TType value, find the appropriate TCompactProtocol.Types constant.
+ return TTypeToCompactType[(int) ttype];
+ }
+
+
+ private async Task<uint> ReadVarInt32Async(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<uint>(cancellationToken);
+ }
+
+ /*
+ Read an i32 from the wire as a varint. The MSB of each byte is set
+ if there is another byte to follow. This can Read up to 5 bytes.
+ */
+
+ uint result = 0;
+ var shift = 0;
+
+ while (true)
+ {
+ var b = (byte) await ReadByteAsync(cancellationToken);
+ result |= (uint) (b & 0x7f) << shift;
+ if ((b & 0x80) != 0x80)
+ {
+ break;
+ }
+ shift += 7;
+ }
+
+ return result;
+ }
+
+ private async Task<ulong> ReadVarInt64Async(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<uint>(cancellationToken);
+ }
+
+ /*
+ Read an i64 from the wire as a proper varint. The MSB of each byte is set
+ if there is another byte to follow. This can Read up to 10 bytes.
+ */
+
+ var shift = 0;
+ ulong result = 0;
+ while (true)
+ {
+ var b = (byte) await ReadByteAsync(cancellationToken);
+ result |= (ulong) (b & 0x7f) << shift;
+ if ((b & 0x80) != 0x80)
+ {
+ break;
+ }
+ shift += 7;
+ }
+
+ return result;
+ }
+
+ private static int ZigzagToInt(uint n)
+ {
+ return (int) (n >> 1) ^ -(int) (n & 1);
+ }
+
+ private static long ZigzagToLong(ulong n)
+ {
+ return (long) (n >> 1) ^ -(long) (n & 1);
+ }
+
+ private static long BytesToLong(byte[] bytes)
+ {
+ /*
+ Note that it's important that the mask bytes are long literals,
+ otherwise they'll default to ints, and when you shift an int left 56 bits,
+ you just get a messed up int.
+ */
+
+ return
+ ((bytes[7] & 0xffL) << 56) |
+ ((bytes[6] & 0xffL) << 48) |
+ ((bytes[5] & 0xffL) << 40) |
+ ((bytes[4] & 0xffL) << 32) |
+ ((bytes[3] & 0xffL) << 24) |
+ ((bytes[2] & 0xffL) << 16) |
+ ((bytes[1] & 0xffL) << 8) |
+ (bytes[0] & 0xffL);
+ }
+
+ private static bool IsBoolType(byte b)
+ {
+ var lowerNibble = b & 0x0f;
+ return (lowerNibble == Types.BooleanTrue) || (lowerNibble == Types.BooleanFalse);
+ }
+
+ private static TType GetTType(byte type)
+ {
+ // Given a TCompactProtocol.Types constant, convert it to its corresponding TType value.
+ switch ((byte) (type & 0x0f))
+ {
+ case Types.Stop:
+ return TType.Stop;
+ case Types.BooleanFalse:
+ case Types.BooleanTrue:
+ return TType.Bool;
+ case Types.Byte:
+ return TType.Byte;
+ case Types.I16:
+ return TType.I16;
+ case Types.I32:
+ return TType.I32;
+ case Types.I64:
+ return TType.I64;
+ case Types.Double:
+ return TType.Double;
+ case Types.Binary:
+ return TType.String;
+ case Types.List:
+ return TType.List;
+ case Types.Set:
+ return TType.Set;
+ case Types.Map:
+ return TType.Map;
+ case Types.Struct:
+ return TType.Struct;
+ default:
+ throw new TProtocolException($"Don't know what exType: {(byte) (type & 0x0f)}");
+ }
+ }
+
+ private static ulong LongToZigzag(long n)
+ {
+ // Convert l into a zigzag long. This allows negative numbers to be represented compactly as a varint
+ return (ulong) (n << 1) ^ (ulong) (n >> 63);
+ }
+
+ private static uint IntToZigzag(int n)
+ {
+ // Convert n into a zigzag int. This allows negative numbers to be represented compactly as a varint
+ return (uint) (n << 1) ^ (uint) (n >> 31);
+ }
+
+ private static void FixedLongToBytes(long n, byte[] buf, int off)
+ {
+ // Convert a long into little-endian bytes in buf starting at off and going until off+7.
+ buf[off + 0] = (byte) (n & 0xff);
+ buf[off + 1] = (byte) ((n >> 8) & 0xff);
+ buf[off + 2] = (byte) ((n >> 16) & 0xff);
+ buf[off + 3] = (byte) ((n >> 24) & 0xff);
+ buf[off + 4] = (byte) ((n >> 32) & 0xff);
+ buf[off + 5] = (byte) ((n >> 40) & 0xff);
+ buf[off + 6] = (byte) ((n >> 48) & 0xff);
+ buf[off + 7] = (byte) ((n >> 56) & 0xff);
+ }
+
+ public class Factory : ITProtocolFactory
+ {
+ public TProtocol GetProtocol(TClientTransport trans)
+ {
+ return new TCompactProtocol(trans);
+ }
+ }
+
+ /// <summary>
+ /// All of the on-wire exType codes.
+ /// </summary>
+ private static class Types
+ {
+ public const byte Stop = 0x00;
+ public const byte BooleanTrue = 0x01;
+ public const byte BooleanFalse = 0x02;
+ public const byte Byte = 0x03;
+ public const byte I16 = 0x04;
+ public const byte I32 = 0x05;
+ public const byte I64 = 0x06;
+ public const byte Double = 0x07;
+ public const byte Binary = 0x08;
+ public const byte List = 0x09;
+ public const byte Set = 0x0A;
+ public const byte Map = 0x0B;
+ public const byte Struct = 0x0C;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Protocols/TJSONProtocol.cs b/lib/netcore/Thrift/Protocols/TJSONProtocol.cs
new file mode 100644
index 000000000..a4ddd5b28
--- /dev/null
+++ b/lib/netcore/Thrift/Protocols/TJSONProtocol.cs
@@ -0,0 +1,1170 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Thrift.Protocols.Entities;
+using Thrift.Protocols.Utilities;
+using Thrift.Transports;
+
+namespace Thrift.Protocols
+{
+ //TODO: implementation of TProtocol
+
+ /// <summary>
+ /// JSON protocol implementation for thrift.
+ /// This is a full-featured protocol supporting Write and Read.
+ /// Please see the C++ class header for a detailed description of the
+ /// protocol's wire format.
+ /// Adapted from the Java version.
+ /// </summary>
+ // ReSharper disable once InconsistentNaming
+ public class TJsonProtocol : TProtocol
+ {
+ private const long Version = 1;
+
+ private const int DefStringSize = 16;
+
+ private static readonly byte[] Comma = {(byte) ','};
+ private static readonly byte[] Colon = {(byte) ':'};
+ private static readonly byte[] Lbrace = {(byte) '{'};
+ private static readonly byte[] Rbrace = {(byte) '}'};
+ private static readonly byte[] Lbracket = {(byte) '['};
+ private static readonly byte[] Rbracket = {(byte) ']'};
+ private static readonly byte[] Quote = {(byte) '"'};
+ private static readonly byte[] Backslash = {(byte) '\\'};
+
+ private static readonly byte[] NameBool = {(byte) 't', (byte) 'f'};
+ private static readonly byte[] NameByte = {(byte) 'i', (byte) '8'};
+ private static readonly byte[] NameI16 = {(byte) 'i', (byte) '1', (byte) '6'};
+ private static readonly byte[] NameI32 = {(byte) 'i', (byte) '3', (byte) '2'};
+ private static readonly byte[] NameI64 = {(byte) 'i', (byte) '6', (byte) '4'};
+ private static readonly byte[] NameDouble = {(byte) 'd', (byte) 'b', (byte) 'l'};
+ private static readonly byte[] NameStruct = {(byte) 'r', (byte) 'e', (byte) 'c'};
+ private static readonly byte[] NameString = {(byte) 's', (byte) 't', (byte) 'r'};
+ private static readonly byte[] NameMap = {(byte) 'm', (byte) 'a', (byte) 'p'};
+ private static readonly byte[] NameList = {(byte) 'l', (byte) 's', (byte) 't'};
+ private static readonly byte[] NameSet = {(byte) 's', (byte) 'e', (byte) 't'};
+
+ private readonly char[] _escapeChars = "\"\\/bfnrt".ToCharArray();
+
+ private readonly byte[] _escapeCharVals =
+ {
+ (byte) '"', (byte) '\\', (byte) '/', (byte) '\b', (byte) '\f', (byte) '\n', (byte) '\r', (byte) '\t'
+ };
+
+ private readonly byte[] _escseq = {(byte) '\\', (byte) 'u', (byte) '0', (byte) '0'};
+
+ private readonly byte[] _jsonCharTable =
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, (byte) 'b', (byte) 't', (byte) 'n', 0, (byte) 'f', (byte) 'r', 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, (byte) '"', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+ };
+
+ // Temporary buffer used by several methods
+ private readonly byte[] _tempBuffer = new byte[4];
+
+ // Current context that we are in
+ protected JsonBaseContext Context;
+
+ // Stack of nested contexts that we may be in
+ protected Stack<JsonBaseContext> ContextStack = new Stack<JsonBaseContext>();
+
+ // Reader that manages a 1-byte buffer
+ protected LookaheadReader Reader;
+
+ // Default encoding
+ protected Encoding Utf8Encoding = Encoding.UTF8;
+
+ /// <summary>
+ /// TJsonProtocol Constructor
+ /// </summary>
+ public TJsonProtocol(TClientTransport trans)
+ : base(trans)
+ {
+ //throw new NotImplementedException("TJsonProtocol is not fully ready for usage");
+
+ Context = new JsonBaseContext(this);
+ Reader = new LookaheadReader(this);
+ }
+
+ private static byte[] GetTypeNameForTypeId(TType typeId)
+ {
+ switch (typeId)
+ {
+ case TType.Bool:
+ return NameBool;
+ case TType.Byte:
+ return NameByte;
+ case TType.I16:
+ return NameI16;
+ case TType.I32:
+ return NameI32;
+ case TType.I64:
+ return NameI64;
+ case TType.Double:
+ return NameDouble;
+ case TType.String:
+ return NameString;
+ case TType.Struct:
+ return NameStruct;
+ case TType.Map:
+ return NameMap;
+ case TType.Set:
+ return NameSet;
+ case TType.List:
+ return NameList;
+ default:
+ throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, "Unrecognized exType");
+ }
+ }
+
+ private static TType GetTypeIdForTypeName(byte[] name)
+ {
+ var result = TType.Stop;
+ if (name.Length > 1)
+ {
+ switch (name[0])
+ {
+ case (byte) 'd':
+ result = TType.Double;
+ break;
+ case (byte) 'i':
+ switch (name[1])
+ {
+ case (byte) '8':
+ result = TType.Byte;
+ break;
+ case (byte) '1':
+ result = TType.I16;
+ break;
+ case (byte) '3':
+ result = TType.I32;
+ break;
+ case (byte) '6':
+ result = TType.I64;
+ break;
+ }
+ break;
+ case (byte) 'l':
+ result = TType.List;
+ break;
+ case (byte) 'm':
+ result = TType.Map;
+ break;
+ case (byte) 'r':
+ result = TType.Struct;
+ break;
+ case (byte) 's':
+ if (name[1] == (byte) 't')
+ {
+ result = TType.String;
+ }
+ else if (name[1] == (byte) 'e')
+ {
+ result = TType.Set;
+ }
+ break;
+ case (byte) 't':
+ result = TType.Bool;
+ break;
+ }
+ }
+ if (result == TType.Stop)
+ {
+ throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, "Unrecognized exType");
+ }
+ return result;
+ }
+
+ /// <summary>
+ /// Push a new JSON context onto the stack.
+ /// </summary>
+ protected void PushContext(JsonBaseContext c)
+ {
+ ContextStack.Push(Context);
+ Context = c;
+ }
+
+ /// <summary>
+ /// Pop the last JSON context off the stack
+ /// </summary>
+ protected void PopContext()
+ {
+ Context = ContextStack.Pop();
+ }
+
+ /// <summary>
+ /// Read a byte that must match b[0]; otherwise an exception is thrown.
+ /// Marked protected to avoid synthetic accessor in JSONListContext.Read
+ /// and JSONPairContext.Read
+ /// </summary>
+ protected async Task ReadJsonSyntaxCharAsync(byte[] b, CancellationToken cancellationToken)
+ {
+ var ch = await Reader.ReadAsync(cancellationToken);
+ if (ch != b[0])
+ {
+ throw new TProtocolException(TProtocolException.INVALID_DATA, $"Unexpected character: {(char) ch}");
+ }
+ }
+
+ /// <summary>
+ /// Convert a byte containing a hex char ('0'-'9' or 'a'-'f') into its
+ /// corresponding hex value
+ /// </summary>
+ private static byte HexVal(byte ch)
+ {
+ if ((ch >= '0') && (ch <= '9'))
+ {
+ return (byte) ((char) ch - '0');
+ }
+
+ if ((ch >= 'a') && (ch <= 'f'))
+ {
+ ch += 10;
+ return (byte) ((char) ch - 'a');
+ }
+
+ throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected hex character");
+ }
+
+ /// <summary>
+ /// Convert a byte containing a hex value to its corresponding hex character
+ /// </summary>
+ private static byte HexChar(byte val)
+ {
+ val &= 0x0F;
+ if (val < 10)
+ {
+ return (byte) ((char) val + '0');
+ }
+ val -= 10;
+ return (byte) ((char) val + 'a');
+ }
+
+ /// <summary>
+ /// Write the bytes in array buf as a JSON characters, escaping as needed
+ /// </summary>
+ private async Task WriteJsonStringAsync(byte[] b, CancellationToken cancellationToken)
+ {
+ await Context.WriteAsync(cancellationToken);
+ await Trans.WriteAsync(Quote, cancellationToken);
+
+ var len = b.Length;
+ for (var i = 0; i < len; i++)
+ {
+ if ((b[i] & 0x00FF) >= 0x30)
+ {
+ if (b[i] == Backslash[0])
+ {
+ await Trans.WriteAsync(Backslash, cancellationToken);
+ await Trans.WriteAsync(Backslash, cancellationToken);
+ }
+ else
+ {
+ await Trans.WriteAsync(b, i, 1, cancellationToken);
+ }
+ }
+ else
+ {
+ _tempBuffer[0] = _jsonCharTable[b[i]];
+ if (_tempBuffer[0] == 1)
+ {
+ await Trans.WriteAsync(b, i, 1, cancellationToken);
+ }
+ else if (_tempBuffer[0] > 1)
+ {
+ await Trans.WriteAsync(Backslash, cancellationToken);
+ await Trans.WriteAsync(_tempBuffer, 0, 1, cancellationToken);
+ }
+ else
+ {
+ await Trans.WriteAsync(_escseq, cancellationToken);
+ _tempBuffer[0] = HexChar((byte) (b[i] >> 4));
+ _tempBuffer[1] = HexChar(b[i]);
+ await Trans.WriteAsync(_tempBuffer, 0, 2, cancellationToken);
+ }
+ }
+ }
+ await Trans.WriteAsync(Quote, cancellationToken);
+ }
+
+ /// <summary>
+ /// Write out number as a JSON value. If the context dictates so, it will be
+ /// wrapped in quotes to output as a JSON string.
+ /// </summary>
+ private async Task WriteJsonIntegerAsync(long num, CancellationToken cancellationToken)
+ {
+ await Context.WriteAsync(cancellationToken);
+ var str = num.ToString();
+
+ var escapeNum = Context.EscapeNumbers();
+ if (escapeNum)
+ {
+ await Trans.WriteAsync(Quote, cancellationToken);
+ }
+
+ await Trans.WriteAsync(Utf8Encoding.GetBytes(str), cancellationToken);
+
+ if (escapeNum)
+ {
+ await Trans.WriteAsync(Quote, cancellationToken);
+ }
+ }
+
+ /// <summary>
+ /// Write out a double as a JSON value. If it is NaN or infinity or if the
+ /// context dictates escaping, Write out as JSON string.
+ /// </summary>
+ private async Task WriteJsonDoubleAsync(double num, CancellationToken cancellationToken)
+ {
+ await Context.WriteAsync(cancellationToken);
+ var str = num.ToString("G17", CultureInfo.InvariantCulture);
+ var special = false;
+
+ switch (str[0])
+ {
+ case 'N': // NaN
+ case 'I': // Infinity
+ special = true;
+ break;
+ case '-':
+ if (str[1] == 'I')
+ {
+ // -Infinity
+ special = true;
+ }
+ break;
+ }
+
+ var escapeNum = special || Context.EscapeNumbers();
+
+ if (escapeNum)
+ {
+ await Trans.WriteAsync(Quote, cancellationToken);
+ }
+
+ await Trans.WriteAsync(Utf8Encoding.GetBytes(str), cancellationToken);
+
+ if (escapeNum)
+ {
+ await Trans.WriteAsync(Quote, cancellationToken);
+ }
+ }
+
+ /// <summary>
+ /// Write out contents of byte array b as a JSON string with base-64 encoded
+ /// data
+ /// </summary>
+ private async Task WriteJsonBase64Async(byte[] b, CancellationToken cancellationToken)
+ {
+ await Context.WriteAsync(cancellationToken);
+ await Trans.WriteAsync(Quote, cancellationToken);
+
+ var len = b.Length;
+ var off = 0;
+
+ // Ignore padding
+ var bound = len >= 2 ? len - 2 : 0;
+
+ for (var i = len - 1; i >= bound && b[i] == '='; --i)
+ {
+ --len;
+ }
+
+ while (len >= 3)
+ {
+ // Encode 3 bytes at a time
+ TBase64Utils.Encode(b, off, 3, _tempBuffer, 0);
+ await Trans.WriteAsync(_tempBuffer, 0, 4, cancellationToken);
+ off += 3;
+ len -= 3;
+ }
+
+ if (len > 0)
+ {
+ // Encode remainder
+ TBase64Utils.Encode(b, off, len, _tempBuffer, 0);
+ await Trans.WriteAsync(_tempBuffer, 0, len + 1, cancellationToken);
+ }
+
+ await Trans.WriteAsync(Quote, cancellationToken);
+ }
+
+ private async Task WriteJsonObjectStartAsync(CancellationToken cancellationToken)
+ {
+ await Context.WriteAsync(cancellationToken);
+ await Trans.WriteAsync(Lbrace, cancellationToken);
+ PushContext(new JsonPairContext(this));
+ }
+
+ private async Task WriteJsonObjectEndAsync(CancellationToken cancellationToken)
+ {
+ PopContext();
+ await Trans.WriteAsync(Rbrace, cancellationToken);
+ }
+
+ private async Task WriteJsonArrayStartAsync(CancellationToken cancellationToken)
+ {
+ await Context.WriteAsync(cancellationToken);
+ await Trans.WriteAsync(Lbracket, cancellationToken);
+ PushContext(new JsonListContext(this));
+ }
+
+ private async Task WriteJsonArrayEndAsync(CancellationToken cancellationToken)
+ {
+ PopContext();
+ await Trans.WriteAsync(Rbracket, cancellationToken);
+ }
+
+ public override async Task WriteMessageBeginAsync(TMessage message, CancellationToken cancellationToken)
+ {
+ await WriteJsonArrayStartAsync(cancellationToken);
+ await WriteJsonIntegerAsync(Version, cancellationToken);
+
+ var b = Utf8Encoding.GetBytes(message.Name);
+ await WriteJsonStringAsync(b, cancellationToken);
+
+ await WriteJsonIntegerAsync((long) message.Type, cancellationToken);
+ await WriteJsonIntegerAsync(message.SeqID, cancellationToken);
+ }
+
+ public override async Task WriteMessageEndAsync(CancellationToken cancellationToken)
+ {
+ await WriteJsonArrayEndAsync(cancellationToken);
+ }
+
+ public override async Task WriteStructBeginAsync(TStruct struc, CancellationToken cancellationToken)
+ {
+ await WriteJsonObjectStartAsync(cancellationToken);
+ }
+
+ public override async Task WriteStructEndAsync(CancellationToken cancellationToken)
+ {
+ await WriteJsonObjectEndAsync(cancellationToken);
+ }
+
+ public override async Task WriteFieldBeginAsync(TField field, CancellationToken cancellationToken)
+ {
+ await WriteJsonIntegerAsync(field.ID, cancellationToken);
+ await WriteJsonObjectStartAsync(cancellationToken);
+ await WriteJsonStringAsync(GetTypeNameForTypeId(field.Type), cancellationToken);
+ }
+
+ public override async Task WriteFieldEndAsync(CancellationToken cancellationToken)
+ {
+ await WriteJsonObjectEndAsync(cancellationToken);
+ }
+
+ public override async Task WriteFieldStopAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override async Task WriteMapBeginAsync(TMap map, CancellationToken cancellationToken)
+ {
+ await WriteJsonArrayStartAsync(cancellationToken);
+ await WriteJsonStringAsync(GetTypeNameForTypeId(map.KeyType), cancellationToken);
+ await WriteJsonStringAsync(GetTypeNameForTypeId(map.ValueType), cancellationToken);
+ await WriteJsonIntegerAsync(map.Count, cancellationToken);
+ await WriteJsonObjectStartAsync(cancellationToken);
+ }
+
+ public override async Task WriteMapEndAsync(CancellationToken cancellationToken)
+ {
+ await WriteJsonObjectEndAsync(cancellationToken);
+ await WriteJsonArrayEndAsync(cancellationToken);
+ }
+
+ public override async Task WriteListBeginAsync(TList list, CancellationToken cancellationToken)
+ {
+ await WriteJsonArrayStartAsync(cancellationToken);
+ await WriteJsonStringAsync(GetTypeNameForTypeId(list.ElementType), cancellationToken);
+ await WriteJsonIntegerAsync(list.Count, cancellationToken);
+ }
+
+ public override async Task WriteListEndAsync(CancellationToken cancellationToken)
+ {
+ await WriteJsonArrayEndAsync(cancellationToken);
+ }
+
+ public override async Task WriteSetBeginAsync(TSet set, CancellationToken cancellationToken)
+ {
+ await WriteJsonArrayStartAsync(cancellationToken);
+ await WriteJsonStringAsync(GetTypeNameForTypeId(set.ElementType), cancellationToken);
+ await WriteJsonIntegerAsync(set.Count, cancellationToken);
+ }
+
+ public override async Task WriteSetEndAsync(CancellationToken cancellationToken)
+ {
+ await WriteJsonArrayEndAsync(cancellationToken);
+ }
+
+ public override async Task WriteBoolAsync(bool b, CancellationToken cancellationToken)
+ {
+ await WriteJsonIntegerAsync(b ? 1 : 0, cancellationToken);
+ }
+
+ public override async Task WriteByteAsync(sbyte b, CancellationToken cancellationToken)
+ {
+ await WriteJsonIntegerAsync(b, cancellationToken);
+ }
+
+ public override async Task WriteI16Async(short i16, CancellationToken cancellationToken)
+ {
+ await WriteJsonIntegerAsync(i16, cancellationToken);
+ }
+
+ public override async Task WriteI32Async(int i32, CancellationToken cancellationToken)
+ {
+ await WriteJsonIntegerAsync(i32, cancellationToken);
+ }
+
+ public override async Task WriteI64Async(long i64, CancellationToken cancellationToken)
+ {
+ await WriteJsonIntegerAsync(i64, cancellationToken);
+ }
+
+ public override async Task WriteDoubleAsync(double d, CancellationToken cancellationToken)
+ {
+ await WriteJsonDoubleAsync(d, cancellationToken);
+ }
+
+ public override async Task WriteStringAsync(string s, CancellationToken cancellationToken)
+ {
+ var b = Utf8Encoding.GetBytes(s);
+ await WriteJsonStringAsync(b, cancellationToken);
+ }
+
+ public override async Task WriteBinaryAsync(byte[] b, CancellationToken cancellationToken)
+ {
+ await WriteJsonBase64Async(b, cancellationToken);
+ }
+
+ /// <summary>
+ /// Read in a JSON string, unescaping as appropriate.. Skip Reading from the
+ /// context if skipContext is true.
+ /// </summary>
+ private async Task<byte[]> ReadJsonStringAsync(bool skipContext, CancellationToken cancellationToken)
+ {
+ using (var buffer = new MemoryStream())
+ {
+ var codeunits = new List<char>();
+
+
+ if (!skipContext)
+ {
+ await Context.ReadAsync(cancellationToken);
+ }
+
+ await ReadJsonSyntaxCharAsync(Quote, cancellationToken);
+
+ while (true)
+ {
+ var ch = await Reader.ReadAsync(cancellationToken);
+ if (ch == Quote[0])
+ {
+ break;
+ }
+
+ // escaped?
+ if (ch != _escseq[0])
+ {
+ await buffer.WriteAsync(new[] {ch}, 0, 1, cancellationToken);
+ continue;
+ }
+
+ // distinguish between \uXXXX and \?
+ ch = await Reader.ReadAsync(cancellationToken);
+ if (ch != _escseq[1]) // control chars like \n
+ {
+ var off = Array.IndexOf(_escapeChars, (char) ch);
+ if (off == -1)
+ {
+ throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected control char");
+ }
+ ch = _escapeCharVals[off];
+ await buffer.WriteAsync(new[] {ch}, 0, 1, cancellationToken);
+ continue;
+ }
+
+
+ // it's \uXXXX
+ await Trans.ReadAllAsync(_tempBuffer, 0, 4, cancellationToken);
+
+ var wch = (short) ((HexVal(_tempBuffer[0]) << 12) +
+ (HexVal(_tempBuffer[1]) << 8) +
+ (HexVal(_tempBuffer[2]) << 4) +
+ HexVal(_tempBuffer[3]));
+
+ if (char.IsHighSurrogate((char) wch))
+ {
+ if (codeunits.Count > 0)
+ {
+ throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected low surrogate char");
+ }
+ codeunits.Add((char) wch);
+ }
+ else if (char.IsLowSurrogate((char) wch))
+ {
+ if (codeunits.Count == 0)
+ {
+ throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected high surrogate char");
+ }
+
+ codeunits.Add((char) wch);
+ var tmp = Utf8Encoding.GetBytes(codeunits.ToArray());
+ await buffer.WriteAsync(tmp, 0, tmp.Length, cancellationToken);
+ codeunits.Clear();
+ }
+ else
+ {
+ var tmp = Utf8Encoding.GetBytes(new[] {(char) wch});
+ await buffer.WriteAsync(tmp, 0, tmp.Length, cancellationToken);
+ }
+ }
+
+ if (codeunits.Count > 0)
+ {
+ throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected low surrogate char");
+ }
+
+ return buffer.ToArray();
+ }
+ }
+
+ /// <summary>
+ /// Return true if the given byte could be a valid part of a JSON number.
+ /// </summary>
+ private static bool IsJsonNumeric(byte b)
+ {
+ switch (b)
+ {
+ case (byte) '+':
+ case (byte) '-':
+ case (byte) '.':
+ case (byte) '0':
+ case (byte) '1':
+ case (byte) '2':
+ case (byte) '3':
+ case (byte) '4':
+ case (byte) '5':
+ case (byte) '6':
+ case (byte) '7':
+ case (byte) '8':
+ case (byte) '9':
+ case (byte) 'E':
+ case (byte) 'e':
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Read in a sequence of characters that are all valid in JSON numbers. Does
+ /// not do a complete regex check to validate that this is actually a number.
+ /// </summary>
+ private async Task<string> ReadJsonNumericCharsAsync(CancellationToken cancellationToken)
+ {
+ var strbld = new StringBuilder();
+ while (true)
+ {
+ var ch = await Reader.PeekAsync(cancellationToken);
+ if (!IsJsonNumeric(ch))
+ {
+ break;
+ }
+ strbld.Append((char) await Reader.ReadAsync(cancellationToken));
+ }
+ return strbld.ToString();
+ }
+
+ /// <summary>
+ /// Read in a JSON number. If the context dictates, Read in enclosing quotes.
+ /// </summary>
+ private async Task<long> ReadJsonIntegerAsync(CancellationToken cancellationToken)
+ {
+ await Context.ReadAsync(cancellationToken);
+ if (Context.EscapeNumbers())
+ {
+ await ReadJsonSyntaxCharAsync(Quote, cancellationToken);
+ }
+
+ var str = await ReadJsonNumericCharsAsync(cancellationToken);
+ if (Context.EscapeNumbers())
+ {
+ await ReadJsonSyntaxCharAsync(Quote, cancellationToken);
+ }
+
+ try
+ {
+ return long.Parse(str);
+ }
+ catch (FormatException)
+ {
+ throw new TProtocolException(TProtocolException.INVALID_DATA, "Bad data encounted in numeric data");
+ }
+ }
+
+ /// <summary>
+ /// Read in a JSON double value. Throw if the value is not wrapped in quotes
+ /// when expected or if wrapped in quotes when not expected.
+ /// </summary>
+ private async Task<double> ReadJsonDoubleAsync(CancellationToken cancellationToken)
+ {
+ await Context.ReadAsync(cancellationToken);
+ if (await Reader.PeekAsync(cancellationToken) == Quote[0])
+ {
+ var arr = await ReadJsonStringAsync(true, cancellationToken);
+ var dub = double.Parse(Utf8Encoding.GetString(arr, 0, arr.Length), CultureInfo.InvariantCulture);
+
+ if (!Context.EscapeNumbers() && !double.IsNaN(dub) && !double.IsInfinity(dub))
+ {
+ // Throw exception -- we should not be in a string in this case
+ throw new TProtocolException(TProtocolException.INVALID_DATA, "Numeric data unexpectedly quoted");
+ }
+
+ return dub;
+ }
+
+ if (Context.EscapeNumbers())
+ {
+ // This will throw - we should have had a quote if escapeNum == true
+ await ReadJsonSyntaxCharAsync(Quote, cancellationToken);
+ }
+
+ try
+ {
+ return double.Parse(await ReadJsonNumericCharsAsync(cancellationToken), CultureInfo.InvariantCulture);
+ }
+ catch (FormatException)
+ {
+ throw new TProtocolException(TProtocolException.INVALID_DATA, "Bad data encounted in numeric data");
+ }
+ }
+
+ /// <summary>
+ /// Read in a JSON string containing base-64 encoded data and decode it.
+ /// </summary>
+ private async Task<byte[]> ReadJsonBase64Async(CancellationToken cancellationToken)
+ {
+ var b = await ReadJsonStringAsync(false, cancellationToken);
+ var len = b.Length;
+ var off = 0;
+ var size = 0;
+
+ // reduce len to ignore fill bytes
+ while ((len > 0) && (b[len - 1] == '='))
+ {
+ --len;
+ }
+
+ // read & decode full byte triplets = 4 source bytes
+ while (len > 4)
+ {
+ // Decode 4 bytes at a time
+ TBase64Utils.Decode(b, off, 4, b, size); // NB: decoded in place
+ off += 4;
+ len -= 4;
+ size += 3;
+ }
+
+ // Don't decode if we hit the end or got a single leftover byte (invalid
+ // base64 but legal for skip of regular string exType)
+ if (len > 1)
+ {
+ // Decode remainder
+ TBase64Utils.Decode(b, off, len, b, size); // NB: decoded in place
+ size += len - 1;
+ }
+
+ // Sadly we must copy the byte[] (any way around this?)
+ var result = new byte[size];
+ Array.Copy(b, 0, result, 0, size);
+ return result;
+ }
+
+ private async Task ReadJsonObjectStartAsync(CancellationToken cancellationToken)
+ {
+ await Context.ReadAsync(cancellationToken);
+ await ReadJsonSyntaxCharAsync(Lbrace, cancellationToken);
+ PushContext(new JsonPairContext(this));
+ }
+
+ private async Task ReadJsonObjectEndAsync(CancellationToken cancellationToken)
+ {
+ await ReadJsonSyntaxCharAsync(Rbrace, cancellationToken);
+ PopContext();
+ }
+
+ private async Task ReadJsonArrayStartAsync(CancellationToken cancellationToken)
+ {
+ await Context.ReadAsync(cancellationToken);
+ await ReadJsonSyntaxCharAsync(Lbracket, cancellationToken);
+ PushContext(new JsonListContext(this));
+ }
+
+ private async Task ReadJsonArrayEndAsync(CancellationToken cancellationToken)
+ {
+ await ReadJsonSyntaxCharAsync(Rbracket, cancellationToken);
+ PopContext();
+ }
+
+ public override async Task<TMessage> ReadMessageBeginAsync(CancellationToken cancellationToken)
+ {
+ var message = new TMessage();
+ await ReadJsonArrayStartAsync(cancellationToken);
+ if (await ReadJsonIntegerAsync(cancellationToken) != Version)
+ {
+ throw new TProtocolException(TProtocolException.BAD_VERSION, "Message contained bad version.");
+ }
+
+ var buf = await ReadJsonStringAsync(false, cancellationToken);
+ message.Name = Utf8Encoding.GetString(buf, 0, buf.Length);
+ message.Type = (TMessageType) await ReadJsonIntegerAsync(cancellationToken);
+ message.SeqID = (int) await ReadJsonIntegerAsync(cancellationToken);
+ return message;
+ }
+
+ public override async Task ReadMessageEndAsync(CancellationToken cancellationToken)
+ {
+ await ReadJsonArrayEndAsync(cancellationToken);
+ }
+
+ public override async Task<TStruct> ReadStructBeginAsync(CancellationToken cancellationToken)
+ {
+ await ReadJsonObjectStartAsync(cancellationToken);
+ return new TStruct();
+ }
+
+ public override async Task ReadStructEndAsync(CancellationToken cancellationToken)
+ {
+ await ReadJsonObjectEndAsync(cancellationToken);
+ }
+
+ public override async Task<TField> ReadFieldBeginAsync(CancellationToken cancellationToken)
+ {
+ var field = new TField();
+ var ch = await Reader.PeekAsync(cancellationToken);
+ if (ch == Rbrace[0])
+ {
+ field.Type = TType.Stop;
+ }
+ else
+ {
+ field.ID = (short) await ReadJsonIntegerAsync(cancellationToken);
+ await ReadJsonObjectStartAsync(cancellationToken);
+ field.Type = GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
+ }
+ return field;
+ }
+
+ public override async Task ReadFieldEndAsync(CancellationToken cancellationToken)
+ {
+ await ReadJsonObjectEndAsync(cancellationToken);
+ }
+
+ public override async Task<TMap> ReadMapBeginAsync(CancellationToken cancellationToken)
+ {
+ var map = new TMap();
+ await ReadJsonArrayStartAsync(cancellationToken);
+ map.KeyType = GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
+ map.ValueType = GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
+ map.Count = (int) await ReadJsonIntegerAsync(cancellationToken);
+ await ReadJsonObjectStartAsync(cancellationToken);
+ return map;
+ }
+
+ public override async Task ReadMapEndAsync(CancellationToken cancellationToken)
+ {
+ await ReadJsonObjectEndAsync(cancellationToken);
+ await ReadJsonArrayEndAsync(cancellationToken);
+ }
+
+ public override async Task<TList> ReadListBeginAsync(CancellationToken cancellationToken)
+ {
+ var list = new TList();
+ await ReadJsonArrayStartAsync(cancellationToken);
+ list.ElementType = GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
+ list.Count = (int) await ReadJsonIntegerAsync(cancellationToken);
+ return list;
+ }
+
+ public override async Task ReadListEndAsync(CancellationToken cancellationToken)
+ {
+ await ReadJsonArrayEndAsync(cancellationToken);
+ }
+
+ public override async Task<TSet> ReadSetBeginAsync(CancellationToken cancellationToken)
+ {
+ var set = new TSet();
+ await ReadJsonArrayStartAsync(cancellationToken);
+ set.ElementType = GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken));
+ set.Count = (int) await ReadJsonIntegerAsync(cancellationToken);
+ return set;
+ }
+
+ public override async Task ReadSetEndAsync(CancellationToken cancellationToken)
+ {
+ await ReadJsonArrayEndAsync(cancellationToken);
+ }
+
+ public override async Task<bool> ReadBoolAsync(CancellationToken cancellationToken)
+ {
+ return await ReadJsonIntegerAsync(cancellationToken) != 0;
+ }
+
+ public override async Task<sbyte> ReadByteAsync(CancellationToken cancellationToken)
+ {
+ return (sbyte) await ReadJsonIntegerAsync(cancellationToken);
+ }
+
+ public override async Task<short> ReadI16Async(CancellationToken cancellationToken)
+ {
+ return (short) await ReadJsonIntegerAsync(cancellationToken);
+ }
+
+ public override async Task<int> ReadI32Async(CancellationToken cancellationToken)
+ {
+ return (int) await ReadJsonIntegerAsync(cancellationToken);
+ }
+
+ public override async Task<long> ReadI64Async(CancellationToken cancellationToken)
+ {
+ return await ReadJsonIntegerAsync(cancellationToken);
+ }
+
+ public override async Task<double> ReadDoubleAsync(CancellationToken cancellationToken)
+ {
+ return await ReadJsonDoubleAsync(cancellationToken);
+ }
+
+ public override async Task<string> ReadStringAsync(CancellationToken cancellationToken)
+ {
+ var buf = await ReadJsonStringAsync(false, cancellationToken);
+ return Utf8Encoding.GetString(buf, 0, buf.Length);
+ }
+
+ public override async Task<byte[]> ReadBinaryAsync(CancellationToken cancellationToken)
+ {
+ return await ReadJsonBase64Async(cancellationToken);
+ }
+
+ /// <summary>
+ /// Factory for JSON protocol objects
+ /// </summary>
+ public class Factory : ITProtocolFactory
+ {
+ public TProtocol GetProtocol(TClientTransport trans)
+ {
+ return new TJsonProtocol(trans);
+ }
+ }
+
+ /// <summary>
+ /// Base class for tracking JSON contexts that may require
+ /// inserting/Reading additional JSON syntax characters
+ /// This base context does nothing.
+ /// </summary>
+ protected class JsonBaseContext
+ {
+ protected TJsonProtocol Proto;
+
+ public JsonBaseContext(TJsonProtocol proto)
+ {
+ Proto = proto;
+ }
+
+ public virtual async Task WriteAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public virtual async Task ReadAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public virtual bool EscapeNumbers()
+ {
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Context for JSON lists. Will insert/Read commas before each item except
+ /// for the first one
+ /// </summary>
+ protected class JsonListContext : JsonBaseContext
+ {
+ private bool _first = true;
+
+ public JsonListContext(TJsonProtocol protocol)
+ : base(protocol)
+ {
+ }
+
+ public override async Task WriteAsync(CancellationToken cancellationToken)
+ {
+ if (_first)
+ {
+ _first = false;
+ }
+ else
+ {
+ await Proto.Trans.WriteAsync(Comma, cancellationToken);
+ }
+ }
+
+ public override async Task ReadAsync(CancellationToken cancellationToken)
+ {
+ if (_first)
+ {
+ _first = false;
+ }
+ else
+ {
+ await Proto.ReadJsonSyntaxCharAsync(Comma, cancellationToken);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Context for JSON records. Will insert/Read colons before the value portion
+ /// of each record pair, and commas before each key except the first. In
+ /// addition, will indicate that numbers in the key position need to be
+ /// escaped in quotes (since JSON keys must be strings).
+ /// </summary>
+ protected class JsonPairContext : JsonBaseContext
+ {
+ private bool _colon = true;
+
+ private bool _first = true;
+
+ public JsonPairContext(TJsonProtocol proto)
+ : base(proto)
+ {
+ }
+
+ public override async Task WriteAsync(CancellationToken cancellationToken)
+ {
+ if (_first)
+ {
+ _first = false;
+ _colon = true;
+ }
+ else
+ {
+ await Proto.Trans.WriteAsync(_colon ? Colon : Comma, cancellationToken);
+ _colon = !_colon;
+ }
+ }
+
+ public override async Task ReadAsync(CancellationToken cancellationToken)
+ {
+ if (_first)
+ {
+ _first = false;
+ _colon = true;
+ }
+ else
+ {
+ await Proto.ReadJsonSyntaxCharAsync(_colon ? Colon : Comma, cancellationToken);
+ _colon = !_colon;
+ }
+ }
+
+ public override bool EscapeNumbers()
+ {
+ return _colon;
+ }
+ }
+
+ /// <summary>
+ /// Holds up to one byte from the transport
+ /// </summary>
+ protected class LookaheadReader
+ {
+ private readonly byte[] _data = new byte[1];
+
+ private bool _hasData;
+ protected TJsonProtocol Proto;
+
+ public LookaheadReader(TJsonProtocol proto)
+ {
+ Proto = proto;
+ }
+
+ /// <summary>
+ /// Return and consume the next byte to be Read, either taking it from the
+ /// data buffer if present or getting it from the transport otherwise.
+ /// </summary>
+ public async Task<byte> ReadAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<byte>(cancellationToken);
+ }
+
+ if (_hasData)
+ {
+ _hasData = false;
+ }
+ else
+ {
+ await Proto.Trans.ReadAllAsync(_data, 0, 1, cancellationToken);
+ }
+ return _data[0];
+ }
+
+ /// <summary>
+ /// Return the next byte to be Read without consuming, filling the data
+ /// buffer if it has not been filled alReady.
+ /// </summary>
+ public async Task<byte> PeekAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<byte>(cancellationToken);
+ }
+
+ if (!_hasData)
+ {
+ await Proto.Trans.ReadAllAsync(_data, 0, 1, cancellationToken);
+ }
+ _hasData = true;
+ return _data[0];
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Protocols/TMultiplexedProtocol.cs b/lib/netcore/Thrift/Protocols/TMultiplexedProtocol.cs
new file mode 100644
index 000000000..5b2202e25
--- /dev/null
+++ b/lib/netcore/Thrift/Protocols/TMultiplexedProtocol.cs
@@ -0,0 +1,100 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System.Threading;
+using System.Threading.Tasks;
+using Thrift.Protocols.Entities;
+
+namespace Thrift.Protocols
+{
+ /**
+ * TMultiplexedProtocol is a protocol-independent concrete decorator that allows a Thrift
+ * client to communicate with a multiplexing Thrift server, by prepending the service name
+ * to the function name during function calls.
+ *
+ * NOTE: THIS IS NOT TO BE USED BY SERVERS.
+ * On the server, use TMultiplexedProcessor to handle requests from a multiplexing client.
+ *
+ * This example uses a single socket transport to invoke two services:
+ *
+ * TSocketClientTransport transport = new TSocketClientTransport("localhost", 9090);
+ * transport.open();
+ *
+ * TBinaryProtocol protocol = new TBinaryProtocol(transport);
+ *
+ * TMultiplexedProtocol mp = new TMultiplexedProtocol(protocol, "Calculator");
+ * Calculator.Client service = new Calculator.Client(mp);
+ *
+ * TMultiplexedProtocol mp2 = new TMultiplexedProtocol(protocol, "WeatherReport");
+ * WeatherReport.Client service2 = new WeatherReport.Client(mp2);
+ *
+ * System.out.println(service.add(2,2));
+ * System.out.println(service2.getTemperature());
+ *
+ */
+
+ //TODO: implementation of TProtocol
+
+ // ReSharper disable once InconsistentNaming
+ public class TMultiplexedProtocol : TProtocolDecorator
+ {
+ /** Used to delimit the service name from the function name */
+ public const string Separator = ":";
+
+ private readonly string _serviceName;
+
+ /**
+ * Wrap the specified protocol, allowing it to be used to communicate with a
+ * multiplexing server. The <code>serviceName</code> is required as it is
+ * prepended to the message header so that the multiplexing server can broker
+ * the function call to the proper service.
+ *
+ * Args:
+ * protocol Your communication protocol of choice, e.g. TBinaryProtocol
+ * serviceName The service name of the service communicating via this protocol.
+ */
+
+ public TMultiplexedProtocol(TProtocol protocol, string serviceName)
+ : base(protocol)
+ {
+ _serviceName = serviceName;
+ }
+
+ /**
+ * Prepends the service name to the function name, separated by TMultiplexedProtocol.SEPARATOR.
+ * Args:
+ * tMessage The original message.
+ */
+
+ public override async Task WriteMessageBeginAsync(TMessage message, CancellationToken cancellationToken)
+ {
+ switch (message.Type)
+ {
+ case TMessageType.Call:
+ case TMessageType.Oneway:
+ await
+ base.WriteMessageBeginAsync(
+ new TMessage($"{_serviceName}{Separator}{message.Name}", message.Type, message.SeqID),
+ cancellationToken);
+ break;
+ default:
+ await base.WriteMessageBeginAsync(message, cancellationToken);
+ break;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Protocols/TProtocol.cs b/lib/netcore/Thrift/Protocols/TProtocol.cs
new file mode 100644
index 000000000..8fef8613b
--- /dev/null
+++ b/lib/netcore/Thrift/Protocols/TProtocol.cs
@@ -0,0 +1,377 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Thrift.Protocols.Entities;
+using Thrift.Transports;
+
+namespace Thrift.Protocols
+{
+ // ReSharper disable once InconsistentNaming
+ public abstract class TProtocol : IDisposable
+ {
+ private const int DefaultRecursionDepth = 64;
+ private bool _isDisposed;
+ protected int RecursionDepth;
+
+ protected TClientTransport Trans;
+
+ protected TProtocol(TClientTransport trans)
+ {
+ Trans = trans;
+ RecursionLimit = DefaultRecursionDepth;
+ RecursionDepth = 0;
+ }
+
+ public TClientTransport Transport => Trans;
+
+ //TODO: check for protected
+ protected int RecursionLimit { get; set; }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ public void IncrementRecursionDepth()
+ {
+ if (RecursionDepth < RecursionLimit)
+ {
+ ++RecursionDepth;
+ }
+ else
+ {
+ throw new TProtocolException(TProtocolException.DEPTH_LIMIT, "Depth limit exceeded");
+ }
+ }
+
+ public void DecrementRecursionDepth()
+ {
+ --RecursionDepth;
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_isDisposed)
+ {
+ if (disposing)
+ {
+ (Trans as IDisposable)?.Dispose();
+ }
+ }
+ _isDisposed = true;
+ }
+
+ public virtual async Task WriteMessageBeginAsync(TMessage message)
+ {
+ await WriteMessageBeginAsync(message, CancellationToken.None);
+ }
+
+ public abstract Task WriteMessageBeginAsync(TMessage message, CancellationToken cancellationToken);
+
+ public virtual async Task WriteMessageEndAsync()
+ {
+ await WriteMessageEndAsync(CancellationToken.None);
+ }
+
+ public abstract Task WriteMessageEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task WriteStructBeginAsync(TStruct struc)
+ {
+ await WriteStructBeginAsync(struc, CancellationToken.None);
+ }
+
+ public abstract Task WriteStructBeginAsync(TStruct struc, CancellationToken cancellationToken);
+
+ public virtual async Task WriteStructEndAsync()
+ {
+ await WriteStructEndAsync(CancellationToken.None);
+ }
+
+ public abstract Task WriteStructEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task WriteFieldBeginAsync(TField field)
+ {
+ await WriteFieldBeginAsync(field, CancellationToken.None);
+ }
+
+ public abstract Task WriteFieldBeginAsync(TField field, CancellationToken cancellationToken);
+
+ public virtual async Task WriteFieldEndAsync()
+ {
+ await WriteFieldEndAsync(CancellationToken.None);
+ }
+
+ public abstract Task WriteFieldEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task WriteFieldStopAsync()
+ {
+ await WriteFieldStopAsync(CancellationToken.None);
+ }
+
+ public abstract Task WriteFieldStopAsync(CancellationToken cancellationToken);
+
+ public virtual async Task WriteMapBeginAsync(TMap map)
+ {
+ await WriteMapBeginAsync(map, CancellationToken.None);
+ }
+
+ public abstract Task WriteMapBeginAsync(TMap map, CancellationToken cancellationToken);
+
+ public virtual async Task WriteMapEndAsync()
+ {
+ await WriteMapEndAsync(CancellationToken.None);
+ }
+
+ public abstract Task WriteMapEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task WriteListBeginAsync(TList list)
+ {
+ await WriteListBeginAsync(list, CancellationToken.None);
+ }
+
+ public abstract Task WriteListBeginAsync(TList list, CancellationToken cancellationToken);
+
+ public virtual async Task WriteListEndAsync()
+ {
+ await WriteListEndAsync(CancellationToken.None);
+ }
+
+ public abstract Task WriteListEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task WriteSetBeginAsync(TSet set)
+ {
+ await WriteSetBeginAsync(set, CancellationToken.None);
+ }
+
+ public abstract Task WriteSetBeginAsync(TSet set, CancellationToken cancellationToken);
+
+ public virtual async Task WriteSetEndAsync()
+ {
+ await WriteSetEndAsync(CancellationToken.None);
+ }
+
+ public abstract Task WriteSetEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task WriteBoolAsync(bool b)
+ {
+ await WriteBoolAsync(b, CancellationToken.None);
+ }
+
+ public abstract Task WriteBoolAsync(bool b, CancellationToken cancellationToken);
+
+ public virtual async Task WriteByteAsync(sbyte b)
+ {
+ await WriteByteAsync(b, CancellationToken.None);
+ }
+
+ public abstract Task WriteByteAsync(sbyte b, CancellationToken cancellationToken);
+
+ public virtual async Task WriteI16Async(short i16)
+ {
+ await WriteI16Async(i16, CancellationToken.None);
+ }
+
+ public abstract Task WriteI16Async(short i16, CancellationToken cancellationToken);
+
+ public virtual async Task WriteI32Async(int i32)
+ {
+ await WriteI32Async(i32, CancellationToken.None);
+ }
+
+ public abstract Task WriteI32Async(int i32, CancellationToken cancellationToken);
+
+ public virtual async Task WriteI64Async(long i64)
+ {
+ await WriteI64Async(i64, CancellationToken.None);
+ }
+
+ public abstract Task WriteI64Async(long i64, CancellationToken cancellationToken);
+
+ public virtual async Task WriteDoubleAsync(double d)
+ {
+ await WriteDoubleAsync(d, CancellationToken.None);
+ }
+
+ public abstract Task WriteDoubleAsync(double d, CancellationToken cancellationToken);
+
+ public virtual async Task WriteStringAsync(string s)
+ {
+ await WriteStringAsync(s, CancellationToken.None);
+ }
+
+ public virtual async Task WriteStringAsync(string s, CancellationToken cancellationToken)
+ {
+ var bytes = Encoding.UTF8.GetBytes(s);
+ await WriteBinaryAsync(bytes, cancellationToken);
+ }
+
+ public virtual async Task WriteBinaryAsync(byte[] b)
+ {
+ await WriteBinaryAsync(b, CancellationToken.None);
+ }
+
+ public abstract Task WriteBinaryAsync(byte[] b, CancellationToken cancellationToken);
+
+ public virtual async Task<TMessage> ReadMessageBeginAsync()
+ {
+ return await ReadMessageBeginAsync(CancellationToken.None);
+ }
+
+ public abstract Task<TMessage> ReadMessageBeginAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadMessageEndAsync()
+ {
+ await ReadMessageEndAsync(CancellationToken.None);
+ }
+
+ public abstract Task ReadMessageEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task<TStruct> ReadStructBeginAsync()
+ {
+ return await ReadStructBeginAsync(CancellationToken.None);
+ }
+
+ public abstract Task<TStruct> ReadStructBeginAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadStructEndAsync()
+ {
+ await ReadStructEndAsync(CancellationToken.None);
+ }
+
+ public abstract Task ReadStructEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task<TField> ReadFieldBeginAsync()
+ {
+ return await ReadFieldBeginAsync(CancellationToken.None);
+ }
+
+ public abstract Task<TField> ReadFieldBeginAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadFieldEndAsync()
+ {
+ await ReadFieldEndAsync(CancellationToken.None);
+ }
+
+ public abstract Task ReadFieldEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task<TMap> ReadMapBeginAsync()
+ {
+ return await ReadMapBeginAsync(CancellationToken.None);
+ }
+
+ public abstract Task<TMap> ReadMapBeginAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadMapEndAsync()
+ {
+ await ReadMapEndAsync(CancellationToken.None);
+ }
+
+ public abstract Task ReadMapEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task<TList> ReadListBeginAsync()
+ {
+ return await ReadListBeginAsync(CancellationToken.None);
+ }
+
+ public abstract Task<TList> ReadListBeginAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadListEndAsync()
+ {
+ await ReadListEndAsync(CancellationToken.None);
+ }
+
+ public abstract Task ReadListEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task<TSet> ReadSetBeginAsync()
+ {
+ return await ReadSetBeginAsync(CancellationToken.None);
+ }
+
+ public abstract Task<TSet> ReadSetBeginAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadSetEndAsync()
+ {
+ await ReadSetEndAsync(CancellationToken.None);
+ }
+
+ public abstract Task ReadSetEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task<bool> ReadBoolAsync()
+ {
+ return await ReadBoolAsync(CancellationToken.None);
+ }
+
+ public abstract Task<bool> ReadBoolAsync(CancellationToken cancellationToken);
+
+ public virtual async Task<sbyte> ReadByteAsync()
+ {
+ return await ReadByteAsync(CancellationToken.None);
+ }
+
+ public abstract Task<sbyte> ReadByteAsync(CancellationToken cancellationToken);
+
+ public virtual async Task<short> ReadI16Async()
+ {
+ return await ReadI16Async(CancellationToken.None);
+ }
+
+ public abstract Task<short> ReadI16Async(CancellationToken cancellationToken);
+
+ public virtual async Task<int> ReadI32Async()
+ {
+ return await ReadI32Async(CancellationToken.None);
+ }
+
+ public abstract Task<int> ReadI32Async(CancellationToken cancellationToken);
+
+ public virtual async Task<long> ReadI64Async()
+ {
+ return await ReadI64Async(CancellationToken.None);
+ }
+
+ public abstract Task<long> ReadI64Async(CancellationToken cancellationToken);
+
+ public virtual async Task<double> ReadDoubleAsync()
+ {
+ return await ReadDoubleAsync(CancellationToken.None);
+ }
+
+ public abstract Task<double> ReadDoubleAsync(CancellationToken cancellationToken);
+
+ public virtual async Task<string> ReadStringAsync()
+ {
+ return await ReadStringAsync(CancellationToken.None);
+ }
+
+ public virtual async Task<string> ReadStringAsync(CancellationToken cancellationToken)
+ {
+ var buf = await ReadBinaryAsync(cancellationToken);
+ return Encoding.UTF8.GetString(buf, 0, buf.Length);
+ }
+
+ public virtual async Task<byte[]> ReadBinaryAsync()
+ {
+ return await ReadBinaryAsync(CancellationToken.None);
+ }
+
+ public abstract Task<byte[]> ReadBinaryAsync(CancellationToken cancellationToken);
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Protocols/TProtocolDecorator.cs b/lib/netcore/Thrift/Protocols/TProtocolDecorator.cs
new file mode 100644
index 000000000..458b1172a
--- /dev/null
+++ b/lib/netcore/Thrift/Protocols/TProtocolDecorator.cs
@@ -0,0 +1,252 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Thrift.Protocols.Entities;
+
+namespace Thrift.Protocols
+{
+ // ReSharper disable once InconsistentNaming
+ /// <summary>
+ /// TProtocolDecorator forwards all requests to an enclosed TProtocol instance,
+ /// providing a way to author concise concrete decorator subclasses.While it has
+ /// no abstract methods, it is marked abstract as a reminder that by itself,
+ /// it does not modify the behaviour of the enclosed TProtocol.
+ /// </summary>
+ public abstract class TProtocolDecorator : TProtocol
+ {
+ private readonly TProtocol _wrappedProtocol;
+
+ protected TProtocolDecorator(TProtocol protocol)
+ : base(protocol.Transport)
+ {
+ if (protocol == null)
+ {
+ throw new ArgumentNullException(nameof(protocol));
+ }
+
+ _wrappedProtocol = protocol;
+ }
+
+ public override async Task WriteMessageBeginAsync(TMessage message, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteMessageBeginAsync(message, cancellationToken);
+ }
+
+ public override async Task WriteMessageEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteMessageEndAsync(cancellationToken);
+ }
+
+ public override async Task WriteStructBeginAsync(TStruct struc, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteStructBeginAsync(struc, cancellationToken);
+ }
+
+ public override async Task WriteStructEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteStructEndAsync(cancellationToken);
+ }
+
+ public override async Task WriteFieldBeginAsync(TField field, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteFieldBeginAsync(field, cancellationToken);
+ }
+
+ public override async Task WriteFieldEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteFieldEndAsync(cancellationToken);
+ }
+
+ public override async Task WriteFieldStopAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteFieldStopAsync(cancellationToken);
+ }
+
+ public override async Task WriteMapBeginAsync(TMap map, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteMapBeginAsync(map, cancellationToken);
+ }
+
+ public override async Task WriteMapEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteMapEndAsync(cancellationToken);
+ }
+
+ public override async Task WriteListBeginAsync(TList list, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteListBeginAsync(list, cancellationToken);
+ }
+
+ public override async Task WriteListEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteListEndAsync(cancellationToken);
+ }
+
+ public override async Task WriteSetBeginAsync(TSet set, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteSetBeginAsync(set, cancellationToken);
+ }
+
+ public override async Task WriteSetEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteSetEndAsync(cancellationToken);
+ }
+
+ public override async Task WriteBoolAsync(bool b, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteBoolAsync(b, cancellationToken);
+ }
+
+ public override async Task WriteByteAsync(sbyte b, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteByteAsync(b, cancellationToken);
+ }
+
+ public override async Task WriteI16Async(short i16, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteI16Async(i16, cancellationToken);
+ }
+
+ public override async Task WriteI32Async(int i32, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteI32Async(i32, cancellationToken);
+ }
+
+ public override async Task WriteI64Async(long i64, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteI64Async(i64, cancellationToken);
+ }
+
+ public override async Task WriteDoubleAsync(double d, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteDoubleAsync(d, cancellationToken);
+ }
+
+ public override async Task WriteStringAsync(string s, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteStringAsync(s, cancellationToken);
+ }
+
+ public override async Task WriteBinaryAsync(byte[] b, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteBinaryAsync(b, cancellationToken);
+ }
+
+ public override async Task<TMessage> ReadMessageBeginAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadMessageBeginAsync(cancellationToken);
+ }
+
+ public override async Task ReadMessageEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.ReadMessageEndAsync(cancellationToken);
+ }
+
+ public override async Task<TStruct> ReadStructBeginAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadStructBeginAsync(cancellationToken);
+ }
+
+ public override async Task ReadStructEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.ReadStructEndAsync(cancellationToken);
+ }
+
+ public override async Task<TField> ReadFieldBeginAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadFieldBeginAsync(cancellationToken);
+ }
+
+ public override async Task ReadFieldEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.ReadFieldEndAsync(cancellationToken);
+ }
+
+ public override async Task<TMap> ReadMapBeginAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadMapBeginAsync(cancellationToken);
+ }
+
+ public override async Task ReadMapEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.ReadMapEndAsync(cancellationToken);
+ }
+
+ public override async Task<TList> ReadListBeginAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadListBeginAsync(cancellationToken);
+ }
+
+ public override async Task ReadListEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.ReadListEndAsync(cancellationToken);
+ }
+
+ public override async Task<TSet> ReadSetBeginAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadSetBeginAsync(cancellationToken);
+ }
+
+ public override async Task ReadSetEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.ReadSetEndAsync(cancellationToken);
+ }
+
+ public override async Task<bool> ReadBoolAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadBoolAsync(cancellationToken);
+ }
+
+ public override async Task<sbyte> ReadByteAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadByteAsync(cancellationToken);
+ }
+
+ public override async Task<short> ReadI16Async(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadI16Async(cancellationToken);
+ }
+
+ public override async Task<int> ReadI32Async(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadI32Async(cancellationToken);
+ }
+
+ public override async Task<long> ReadI64Async(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadI64Async(cancellationToken);
+ }
+
+ public override async Task<double> ReadDoubleAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadDoubleAsync(cancellationToken);
+ }
+
+ public override async Task<string> ReadStringAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadStringAsync(cancellationToken);
+ }
+
+ public override async Task<byte[]> ReadBinaryAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadBinaryAsync(cancellationToken);
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Protocols/TProtocolException.cs b/lib/netcore/Thrift/Protocols/TProtocolException.cs
new file mode 100644
index 000000000..02d0d3f31
--- /dev/null
+++ b/lib/netcore/Thrift/Protocols/TProtocolException.cs
@@ -0,0 +1,58 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+namespace Thrift.Protocols
+{
+ public class TProtocolException : TException
+ {
+ // do not rename public contants - they used in generated files
+ public const int UNKNOWN = 0;
+ public const int INVALID_DATA = 1;
+ public const int NEGATIVE_SIZE = 2;
+ public const int SIZE_LIMIT = 3;
+ public const int BAD_VERSION = 4;
+ public const int NOT_IMPLEMENTED = 5;
+ public const int DEPTH_LIMIT = 6;
+
+ protected int Type = UNKNOWN;
+
+ public TProtocolException()
+ {
+ }
+
+ public TProtocolException(int type)
+ {
+ Type = type;
+ }
+
+ public TProtocolException(int type, string message)
+ : base(message)
+ {
+ Type = type;
+ }
+
+ public TProtocolException(string message)
+ : base(message)
+ {
+ }
+
+ public int GetExceptionType()
+ {
+ return Type;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Protocols/Utilities/TBase64Utils.cs b/lib/netcore/Thrift/Protocols/Utilities/TBase64Utils.cs
new file mode 100644
index 000000000..15fd45cbe
--- /dev/null
+++ b/lib/netcore/Thrift/Protocols/Utilities/TBase64Utils.cs
@@ -0,0 +1,101 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+
+namespace Thrift.Protocols.Utilities
+{
+ // ReSharper disable once InconsistentNaming
+ internal static class TBase64Utils
+ {
+ //TODO: Constants
+ //TODO: Check for args
+ //TODO: Unitests
+
+ internal const string EncodeTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+ private static readonly int[] DecodeTable =
+ {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+ };
+
+ internal static void Encode(byte[] src, int srcOff, int len, byte[] dst, int dstOff)
+ {
+ if (src == null)
+ {
+ throw new ArgumentNullException(nameof(src));
+ }
+
+ dst[dstOff] = (byte) EncodeTable[(src[srcOff] >> 2) & 0x3F];
+
+ if (len == 3)
+ {
+ dst[dstOff + 1] = (byte) EncodeTable[((src[srcOff] << 4) & 0x30) | ((src[srcOff + 1] >> 4) & 0x0F)];
+ dst[dstOff + 2] = (byte) EncodeTable[((src[srcOff + 1] << 2) & 0x3C) | ((src[srcOff + 2] >> 6) & 0x03)];
+ dst[dstOff + 3] = (byte) EncodeTable[src[srcOff + 2] & 0x3F];
+ }
+ else if (len == 2)
+ {
+ dst[dstOff + 1] = (byte) EncodeTable[((src[srcOff] << 4) & 0x30) | ((src[srcOff + 1] >> 4) & 0x0F)];
+ dst[dstOff + 2] = (byte) EncodeTable[(src[srcOff + 1] << 2) & 0x3C];
+ }
+ else
+ {
+ // len == 1
+ dst[dstOff + 1] = (byte) EncodeTable[(src[srcOff] << 4) & 0x30];
+ }
+ }
+
+ internal static void Decode(byte[] src, int srcOff, int len, byte[] dst, int dstOff)
+ {
+ if (src == null)
+ {
+ throw new ArgumentNullException(nameof(src));
+ }
+
+ dst[dstOff] = (byte) ((DecodeTable[src[srcOff] & 0x0FF] << 2) | (DecodeTable[src[srcOff + 1] & 0x0FF] >> 4));
+
+ if (len > 2)
+ {
+ dst[dstOff + 1] =
+ (byte)
+ (((DecodeTable[src[srcOff + 1] & 0x0FF] << 4) & 0xF0) | (DecodeTable[src[srcOff + 2] & 0x0FF] >> 2));
+ if (len > 3)
+ {
+ dst[dstOff + 2] =
+ (byte)
+ (((DecodeTable[src[srcOff + 2] & 0x0FF] << 6) & 0xC0) | DecodeTable[src[srcOff + 3] & 0x0FF]);
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Protocols/Utilities/TProtocolUtil.cs b/lib/netcore/Thrift/Protocols/Utilities/TProtocolUtil.cs
new file mode 100644
index 000000000..623214964
--- /dev/null
+++ b/lib/netcore/Thrift/Protocols/Utilities/TProtocolUtil.cs
@@ -0,0 +1,108 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System.Threading;
+using System.Threading.Tasks;
+using Thrift.Protocols.Entities;
+
+namespace Thrift.Protocols
+{
+ // ReSharper disable once InconsistentNaming
+ public static class TProtocolUtil
+ {
+ public static async Task SkipAsync(TProtocol prot, TType type, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+
+ prot.IncrementRecursionDepth();
+ try
+ {
+ switch (type)
+ {
+ case TType.Bool:
+ await prot.ReadBoolAsync(cancellationToken);
+ break;
+ case TType.Byte:
+ await prot.ReadByteAsync(cancellationToken);
+ break;
+ case TType.I16:
+ await prot.ReadI16Async(cancellationToken);
+ break;
+ case TType.I32:
+ await prot.ReadI32Async(cancellationToken);
+ break;
+ case TType.I64:
+ await prot.ReadI64Async(cancellationToken);
+ break;
+ case TType.Double:
+ await prot.ReadDoubleAsync(cancellationToken);
+ break;
+ case TType.String:
+ // Don't try to decode the string, just skip it.
+ await prot.ReadBinaryAsync(cancellationToken);
+ break;
+ case TType.Struct:
+ await prot.ReadStructBeginAsync(cancellationToken);
+ while (true)
+ {
+ var field = await prot.ReadFieldBeginAsync(cancellationToken);
+ if (field.Type == TType.Stop)
+ {
+ break;
+ }
+ await SkipAsync(prot, field.Type, cancellationToken);
+ await prot.ReadFieldEndAsync(cancellationToken);
+ }
+ await prot.ReadStructEndAsync(cancellationToken);
+ break;
+ case TType.Map:
+ var map = await prot.ReadMapBeginAsync(cancellationToken);
+ for (var i = 0; i < map.Count; i++)
+ {
+ await SkipAsync(prot, map.KeyType, cancellationToken);
+ await SkipAsync(prot, map.ValueType, cancellationToken);
+ }
+ await prot.ReadMapEndAsync(cancellationToken);
+ break;
+ case TType.Set:
+ var set = await prot.ReadSetBeginAsync(cancellationToken);
+ for (var i = 0; i < set.Count; i++)
+ {
+ await SkipAsync(prot, set.ElementType, cancellationToken);
+ }
+ await prot.ReadSetEndAsync(cancellationToken);
+ break;
+ case TType.List:
+ var list = await prot.ReadListBeginAsync(cancellationToken);
+ for (var i = 0; i < list.Count; i++)
+ {
+ await SkipAsync(prot, list.ElementType, cancellationToken);
+ }
+ await prot.ReadListEndAsync(cancellationToken);
+ break;
+ }
+ }
+ finally
+ {
+ prot.DecrementRecursionDepth();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Server/AsyncBaseServer.cs b/lib/netcore/Thrift/Server/AsyncBaseServer.cs
new file mode 100644
index 000000000..5ff7a32a2
--- /dev/null
+++ b/lib/netcore/Thrift/Server/AsyncBaseServer.cs
@@ -0,0 +1,184 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Thrift.Protocols;
+using Thrift.Transports;
+
+namespace Thrift.Server
+{
+ //TODO: unhandled exceptions, etc.
+
+ // ReSharper disable once InconsistentNaming
+ public class AsyncBaseServer : TBaseServer
+ {
+ private readonly int _clientWaitingDelay;
+ private volatile Task _serverTask;
+
+ public AsyncBaseServer(ITAsyncProcessor processor, TServerTransport serverTransport,
+ ITProtocolFactory inputProtocolFactory, ITProtocolFactory outputProtocolFactory,
+ ILoggerFactory loggerFactory, int clientWaitingDelay = 10)
+ : this(new SingletonTProcessorFactory(processor), serverTransport,
+ new TTransportFactory(), new TTransportFactory(),
+ inputProtocolFactory, outputProtocolFactory,
+ loggerFactory.CreateLogger(nameof(AsyncBaseServer)), clientWaitingDelay)
+ {
+ }
+
+ public AsyncBaseServer(ITProcessorFactory itProcessorFactory, TServerTransport serverTransport,
+ TTransportFactory inputTransportFactory, TTransportFactory outputTransportFactory,
+ ITProtocolFactory inputProtocolFactory, ITProtocolFactory outputProtocolFactory,
+ ILogger logger, int clientWaitingDelay = 10)
+ : base(itProcessorFactory, serverTransport, inputTransportFactory, outputTransportFactory,
+ inputProtocolFactory, outputProtocolFactory, logger)
+ {
+ _clientWaitingDelay = clientWaitingDelay;
+ }
+
+ public override async Task ServeAsync(CancellationToken cancellationToken)
+ {
+ try
+ {
+ // cancelation token
+ _serverTask = Task.Factory.StartNew(() => StartListening(cancellationToken),
+ TaskCreationOptions.LongRunning);
+ await _serverTask;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex.ToString());
+ }
+ }
+
+ private async Task StartListening(CancellationToken cancellationToken)
+ {
+ ServerTransport.Listen();
+
+ Logger.LogTrace("Started listening at server");
+
+ if (ServerEventHandler != null)
+ {
+ await ServerEventHandler.PreServeAsync(cancellationToken);
+ }
+
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ if (ServerTransport.IsClientPending())
+ {
+ Logger.LogTrace("Waiting for client connection");
+
+ try
+ {
+ var client = await ServerTransport.AcceptAsync(cancellationToken);
+ await Task.Factory.StartNew(() => Execute(client, cancellationToken));
+ }
+ catch (TTransportException ttx)
+ {
+ Logger.LogTrace($"Transport exception: {ttx}");
+
+ if (ttx.Type != TTransportException.ExceptionType.Interrupted)
+ {
+ Logger.LogError(ttx.ToString());
+ }
+ }
+ }
+ else
+ {
+ await Task.Delay(TimeSpan.FromMilliseconds(_clientWaitingDelay), cancellationToken);
+ }
+ }
+
+ ServerTransport.Close();
+
+ Logger.LogTrace("Completed listening at server");
+ }
+
+ public override void Stop()
+ {
+ }
+
+ private async Task Execute(TClientTransport client, CancellationToken cancellationToken)
+ {
+ Logger.LogTrace("Started client request processing");
+
+ var processor = ItProcessorFactory.GetAsyncProcessor(client, this);
+
+ TClientTransport inputTransport = null;
+ TClientTransport outputTransport = null;
+ TProtocol inputProtocol = null;
+ TProtocol outputProtocol = null;
+ object connectionContext = null;
+
+ try
+ {
+ inputTransport = InputTransportFactory.GetTransport(client);
+ outputTransport = OutputTransportFactory.GetTransport(client);
+
+ inputProtocol = InputProtocolFactory.GetProtocol(inputTransport);
+ outputProtocol = OutputProtocolFactory.GetProtocol(outputTransport);
+
+ if (ServerEventHandler != null)
+ {
+ connectionContext =
+ await ServerEventHandler.CreateContextAsync(inputProtocol, outputProtocol, cancellationToken);
+ }
+
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ if (!await inputTransport.PeekAsync(cancellationToken))
+ {
+ break;
+ }
+
+ if (ServerEventHandler != null)
+ {
+ await
+ ServerEventHandler.ProcessContextAsync(connectionContext, inputTransport, cancellationToken);
+ }
+
+ if (!await processor.ProcessAsync(inputProtocol, outputProtocol, cancellationToken))
+ {
+ break;
+ }
+ }
+ }
+ catch (TTransportException ttx)
+ {
+ Logger.LogTrace($"Transport exception: {ttx}");
+ }
+ catch (Exception x)
+ {
+ Logger.LogError($"Error: {x}");
+ }
+
+ if (ServerEventHandler != null)
+ {
+ await
+ ServerEventHandler.DeleteContextAsync(connectionContext, inputProtocol, outputProtocol,
+ cancellationToken);
+ }
+
+ inputTransport?.Close();
+ outputTransport?.Close();
+
+ Logger.LogTrace("Completed client request processing");
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Server/TBaseServer.cs b/lib/netcore/Thrift/Server/TBaseServer.cs
new file mode 100644
index 000000000..97cc7ff9e
--- /dev/null
+++ b/lib/netcore/Thrift/Server/TBaseServer.cs
@@ -0,0 +1,86 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Thrift.Protocols;
+using Thrift.Transports;
+
+namespace Thrift.Server
+{
+ // ReSharper disable once InconsistentNaming
+ public abstract class TBaseServer
+ {
+ protected readonly ILogger Logger;
+ protected ITProtocolFactory InputProtocolFactory;
+ protected TTransportFactory InputTransportFactory;
+ protected ITProcessorFactory ItProcessorFactory;
+ protected ITProtocolFactory OutputProtocolFactory;
+ protected TTransportFactory OutputTransportFactory;
+
+ protected TServerEventHandler ServerEventHandler;
+ protected TServerTransport ServerTransport;
+
+ protected TBaseServer(ITProcessorFactory itProcessorFactory, TServerTransport serverTransport,
+ TTransportFactory inputTransportFactory, TTransportFactory outputTransportFactory,
+ ITProtocolFactory inputProtocolFactory, ITProtocolFactory outputProtocolFactory,
+ ILogger logger)
+ {
+ if (itProcessorFactory == null) throw new ArgumentNullException(nameof(itProcessorFactory));
+ if (inputTransportFactory == null) throw new ArgumentNullException(nameof(inputTransportFactory));
+ if (outputTransportFactory == null) throw new ArgumentNullException(nameof(outputTransportFactory));
+ if (inputProtocolFactory == null) throw new ArgumentNullException(nameof(inputProtocolFactory));
+ if (outputProtocolFactory == null) throw new ArgumentNullException(nameof(outputProtocolFactory));
+ if (logger == null) throw new ArgumentNullException(nameof(logger));
+
+ ItProcessorFactory = itProcessorFactory;
+ ServerTransport = serverTransport;
+ InputTransportFactory = inputTransportFactory;
+ OutputTransportFactory = outputTransportFactory;
+ InputProtocolFactory = inputProtocolFactory;
+ OutputProtocolFactory = outputProtocolFactory;
+ Logger = logger;
+ }
+
+ public void SetEventHandler(TServerEventHandler seh)
+ {
+ ServerEventHandler = seh;
+ }
+
+ public TServerEventHandler GetEventHandler()
+ {
+ return ServerEventHandler;
+ }
+
+ public abstract void Stop();
+
+ public virtual void Start()
+ {
+ // do nothing
+ }
+
+ public virtual async Task ServeAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Server/TServerEventHandler.cs b/lib/netcore/Thrift/Server/TServerEventHandler.cs
new file mode 100644
index 000000000..733bb4bef
--- /dev/null
+++ b/lib/netcore/Thrift/Server/TServerEventHandler.cs
@@ -0,0 +1,54 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System.Threading;
+using System.Threading.Tasks;
+using Thrift.Protocols;
+using Thrift.Transports;
+
+namespace Thrift.Server
+{
+ //TODO: replacement by event?
+
+ /// <summary>
+ /// Interface implemented by server users to handle events from the server
+ /// </summary>
+ // ReSharper disable once InconsistentNaming
+ public interface TServerEventHandler
+ {
+ /// <summary>
+ /// Called before the server begins */
+ /// </summary>
+ Task PreServeAsync(CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Called when a new client has connected and is about to being processing */
+ /// </summary>
+ Task<object> CreateContextAsync(TProtocol input, TProtocol output, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Called when a client has finished request-handling to delete server context */
+ /// </summary>
+ Task DeleteContextAsync(object serverContext, TProtocol input, TProtocol output,
+ CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Called when a client is about to call the processor */
+ /// </summary>
+ Task ProcessContextAsync(object serverContext, TClientTransport transport, CancellationToken cancellationToken);
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/SingletonTProcessorFactory.cs b/lib/netcore/Thrift/SingletonTProcessorFactory.cs
new file mode 100644
index 000000000..c35123348
--- /dev/null
+++ b/lib/netcore/Thrift/SingletonTProcessorFactory.cs
@@ -0,0 +1,38 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using Thrift.Server;
+using Thrift.Transports;
+
+namespace Thrift
+{
+ // ReSharper disable once InconsistentNaming
+ public class SingletonTProcessorFactory : ITProcessorFactory
+ {
+ private readonly ITAsyncProcessor _tAsyncProcessor;
+
+ public SingletonTProcessorFactory(ITAsyncProcessor tAsyncProcessor)
+ {
+ _tAsyncProcessor = tAsyncProcessor;
+ }
+
+ public ITAsyncProcessor GetAsyncProcessor(TClientTransport trans, TBaseServer baseServer = null)
+ {
+ return _tAsyncProcessor;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/TApplicationException.cs b/lib/netcore/Thrift/TApplicationException.cs
new file mode 100644
index 000000000..f1ea25258
--- /dev/null
+++ b/lib/netcore/Thrift/TApplicationException.cs
@@ -0,0 +1,149 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System.Threading;
+using System.Threading.Tasks;
+using Thrift.Protocols;
+using Thrift.Protocols.Entities;
+
+namespace Thrift
+{
+ // ReSharper disable once InconsistentNaming
+ public class TApplicationException : TException
+ {
+ public enum ExceptionType
+ {
+ Unknown,
+ UnknownMethod,
+ InvalidMessageType,
+ WrongMethodName,
+ BadSequenceId,
+ MissingResult,
+ InternalError,
+ ProtocolError,
+ InvalidTransform,
+ InvalidProtocol,
+ UnsupportedClientType
+ }
+
+ private const int MessageTypeFieldId = 1;
+ private const int ExTypeFieldId = 2;
+
+ protected ExceptionType Type;
+
+ public TApplicationException()
+ {
+ }
+
+ public TApplicationException(ExceptionType type)
+ {
+ Type = type;
+ }
+
+ public TApplicationException(ExceptionType type, string message)
+ : base(message)
+ {
+ Type = type;
+ }
+
+ public static async Task<TApplicationException> ReadAsync(TProtocol iprot, CancellationToken cancellationToken)
+ {
+ string message = null;
+ var type = ExceptionType.Unknown;
+
+ await iprot.ReadStructBeginAsync(cancellationToken);
+ while (true)
+ {
+ var field = await iprot.ReadFieldBeginAsync(cancellationToken);
+ if (field.Type == TType.Stop)
+ {
+ break;
+ }
+
+ switch (field.ID)
+ {
+ case MessageTypeFieldId:
+ if (field.Type == TType.String)
+ {
+ message = await iprot.ReadStringAsync(cancellationToken);
+ }
+ else
+ {
+ await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);
+ }
+ break;
+ case ExTypeFieldId:
+ if (field.Type == TType.I32)
+ {
+ type = (ExceptionType) await iprot.ReadI32Async(cancellationToken);
+ }
+ else
+ {
+ await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);
+ }
+ break;
+ default:
+ await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);
+ break;
+ }
+
+ await iprot.ReadFieldEndAsync(cancellationToken);
+ }
+
+ await iprot.ReadStructEndAsync(cancellationToken);
+
+ return new TApplicationException(type, message);
+ }
+
+ public async Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+
+ const string messageTypeFieldName = "message";
+ const string exTypeFieldName = "exType";
+ const string structApplicationExceptionName = "TApplicationException";
+
+ var struc = new TStruct(structApplicationExceptionName);
+ var field = new TField();
+
+ await oprot.WriteStructBeginAsync(struc, cancellationToken);
+
+ if (!string.IsNullOrEmpty(Message))
+ {
+ field.Name = messageTypeFieldName;
+ field.Type = TType.String;
+ field.ID = MessageTypeFieldId;
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteStringAsync(Message, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+ }
+
+ field.Name = exTypeFieldName;
+ field.Type = TType.I32;
+ field.ID = ExTypeFieldId;
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteI32Async((int) Type, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+ await oprot.WriteFieldStopAsync(cancellationToken);
+ await oprot.WriteStructEndAsync(cancellationToken);
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/TBaseClient.cs b/lib/netcore/Thrift/TBaseClient.cs
new file mode 100644
index 000000000..24e08cef9
--- /dev/null
+++ b/lib/netcore/Thrift/TBaseClient.cs
@@ -0,0 +1,98 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Thrift.Protocols;
+
+namespace Thrift
+{
+ /// <summary>
+ /// TBaseClient.
+ /// Base client for generated clients.
+ /// Do not change this class without checking generated code (namings, etc.)
+ /// </summary>
+ public abstract class TBaseClient
+ {
+ private readonly TProtocol _inputProtocol;
+ private readonly TProtocol _outputProtocol;
+ private bool _isDisposed;
+ private int _seqId;
+
+ protected TBaseClient(TProtocol inputProtocol, TProtocol outputProtocol)
+ {
+ if (inputProtocol == null)
+ {
+ throw new ArgumentNullException(nameof(inputProtocol));
+ }
+
+ if (outputProtocol == null)
+ {
+ throw new ArgumentNullException(nameof(outputProtocol));
+ }
+
+ _inputProtocol = inputProtocol;
+ _outputProtocol = outputProtocol;
+ }
+
+ public TProtocol InputProtocol => _inputProtocol;
+
+ public TProtocol OutputProtocol => _outputProtocol;
+
+ public int SeqId => _seqId;
+
+ public virtual async Task OpenTransportAsync()
+ {
+ await OpenTransportAsync(CancellationToken.None);
+ }
+
+ public virtual async Task OpenTransportAsync(CancellationToken cancellationToken)
+ {
+ if (!_inputProtocol.Transport.IsOpen)
+ {
+ await _inputProtocol.Transport.OpenAsync(cancellationToken);
+ }
+
+ if (!_inputProtocol.Transport.IsOpen)
+ {
+ await _outputProtocol.Transport.OpenAsync(cancellationToken);
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_isDisposed)
+ {
+ if (disposing)
+ {
+ _inputProtocol?.Dispose();
+ _outputProtocol?.Dispose();
+ }
+ }
+
+ _isDisposed = true;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/TException.cs b/lib/netcore/Thrift/TException.cs
new file mode 100644
index 000000000..6aa588d7f
--- /dev/null
+++ b/lib/netcore/Thrift/TException.cs
@@ -0,0 +1,34 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+
+namespace Thrift
+{
+ // ReSharper disable once InconsistentNaming
+ public class TException : Exception
+ {
+ public TException()
+ {
+ }
+
+ public TException(string message)
+ : base(message)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/TMultiplexedProcessor.cs b/lib/netcore/Thrift/TMultiplexedProcessor.cs
new file mode 100644
index 000000000..ad0e749e0
--- /dev/null
+++ b/lib/netcore/Thrift/TMultiplexedProcessor.cs
@@ -0,0 +1,143 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Thrift.Protocols;
+using Thrift.Protocols.Entities;
+
+namespace Thrift
+{
+ // ReSharper disable once InconsistentNaming
+ public class TMultiplexedProcessor : ITAsyncProcessor
+ {
+ //TODO: Localization
+
+ private readonly Dictionary<string, ITAsyncProcessor> _serviceProcessorMap =
+ new Dictionary<string, ITAsyncProcessor>();
+
+ public async Task<bool> ProcessAsync(TProtocol iprot, TProtocol oprot)
+ {
+ return await ProcessAsync(iprot, oprot, CancellationToken.None);
+ }
+
+ public async Task<bool> ProcessAsync(TProtocol iprot, TProtocol oprot, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<bool>(cancellationToken);
+ }
+
+ try
+ {
+ var message = await iprot.ReadMessageBeginAsync(cancellationToken);
+
+ if ((message.Type != TMessageType.Call) && (message.Type != TMessageType.Oneway))
+ {
+ await FailAsync(oprot, message, TApplicationException.ExceptionType.InvalidMessageType,
+ "Message exType CALL or ONEWAY expected", cancellationToken);
+ return false;
+ }
+
+ // Extract the service name
+ var index = message.Name.IndexOf(TMultiplexedProtocol.Separator, StringComparison.Ordinal);
+ if (index < 0)
+ {
+ await FailAsync(oprot, message, TApplicationException.ExceptionType.InvalidProtocol,
+ $"Service name not found in message name: {message.Name}. Did you forget to use a TMultiplexProtocol in your client?",
+ cancellationToken);
+ return false;
+ }
+
+ // Create a new TMessage, something that can be consumed by any TProtocol
+ var serviceName = message.Name.Substring(0, index);
+ ITAsyncProcessor actualProcessor;
+ if (!_serviceProcessorMap.TryGetValue(serviceName, out actualProcessor))
+ {
+ await FailAsync(oprot, message, TApplicationException.ExceptionType.InternalError,
+ $"Service name not found: {serviceName}. Did you forget to call RegisterProcessor()?",
+ cancellationToken);
+ return false;
+ }
+
+ // Create a new TMessage, removing the service name
+ var newMessage = new TMessage(
+ message.Name.Substring(serviceName.Length + TMultiplexedProtocol.Separator.Length),
+ message.Type,
+ message.SeqID);
+
+ // Dispatch processing to the stored processor
+ return
+ await
+ actualProcessor.ProcessAsync(new StoredMessageProtocol(iprot, newMessage), oprot,
+ cancellationToken);
+ }
+ catch (IOException)
+ {
+ return false; // similar to all other processors
+ }
+ }
+
+ public void RegisterProcessor(string serviceName, ITAsyncProcessor processor)
+ {
+ if (_serviceProcessorMap.ContainsKey(serviceName))
+ {
+ throw new InvalidOperationException(
+ $"Processor map already contains processor with name: '{serviceName}'");
+ }
+
+ _serviceProcessorMap.Add(serviceName, processor);
+ }
+
+ private async Task FailAsync(TProtocol oprot, TMessage message, TApplicationException.ExceptionType extype,
+ string etxt, CancellationToken cancellationToken)
+ {
+ var appex = new TApplicationException(extype, etxt);
+
+ var newMessage = new TMessage(message.Name, TMessageType.Exception, message.SeqID);
+
+ await oprot.WriteMessageBeginAsync(newMessage, cancellationToken);
+ await appex.WriteAsync(oprot, cancellationToken);
+ await oprot.WriteMessageEndAsync(cancellationToken);
+ await oprot.Transport.FlushAsync(cancellationToken);
+ }
+
+ private class StoredMessageProtocol : TProtocolDecorator
+ {
+ readonly TMessage _msgBegin;
+
+ public StoredMessageProtocol(TProtocol protocol, TMessage messageBegin)
+ : base(protocol)
+ {
+ _msgBegin = messageBegin;
+ }
+
+ public override async Task<TMessage> ReadMessageBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<TMessage>(cancellationToken);
+ }
+
+ return _msgBegin;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Thrift.xproj b/lib/netcore/Thrift/Thrift.xproj
new file mode 100644
index 000000000..b450b9207
--- /dev/null
+++ b/lib/netcore/Thrift/Thrift.xproj
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
+ <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
+ </PropertyGroup>
+
+ <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>6850cf46-5467-4c65-bd78-871581c539fc</ProjectGuid>
+ <RootNamespace>Thrift</RootNamespace>
+ <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
+ <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
+ <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <SchemaVersion>2.0</SchemaVersion>
+ </PropertyGroup>
+ <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
+</Project> \ No newline at end of file
diff --git a/lib/netcore/Thrift/Transports/Client/TBufferedClientTransport.cs b/lib/netcore/Thrift/Transports/Client/TBufferedClientTransport.cs
new file mode 100644
index 000000000..86eb735dc
--- /dev/null
+++ b/lib/netcore/Thrift/Transports/Client/TBufferedClientTransport.cs
@@ -0,0 +1,210 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Thrift.Transports.Client
+{
+ // ReSharper disable once InconsistentNaming
+ public class TBufferedClientTransport : TClientTransport
+ {
+ private readonly int _bufSize;
+ private readonly MemoryStream _inputBuffer = new MemoryStream(0);
+ private readonly MemoryStream _outputBuffer = new MemoryStream(0);
+ private readonly TClientTransport _transport;
+ private bool _isDisposed;
+
+ //TODO: should support only specified input transport?
+ public TBufferedClientTransport(TClientTransport transport, int bufSize = 1024)
+ {
+ if (transport == null)
+ {
+ throw new ArgumentNullException(nameof(transport));
+ }
+
+ if (bufSize <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(bufSize), "Buffer size must be a positive number.");
+ }
+
+ _transport = transport;
+ _bufSize = bufSize;
+ }
+
+ public TClientTransport UnderlyingTransport
+ {
+ get
+ {
+ CheckNotDisposed();
+
+ return _transport;
+ }
+ }
+
+ public override bool IsOpen => !_isDisposed && _transport.IsOpen;
+
+ public override async Task OpenAsync(CancellationToken cancellationToken)
+ {
+ CheckNotDisposed();
+
+ await _transport.OpenAsync(cancellationToken);
+ }
+
+ public override void Close()
+ {
+ CheckNotDisposed();
+
+ _transport.Close();
+ }
+
+ public override async Task<int> ReadAsync(byte[] buffer, int offset, int length,
+ CancellationToken cancellationToken)
+ {
+ //TODO: investigate how it should work correctly
+ CheckNotDisposed();
+
+ ValidateBufferArgs(buffer, offset, length);
+
+ if (!IsOpen)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.NotOpen);
+ }
+
+ if (_inputBuffer.Capacity < _bufSize)
+ {
+ _inputBuffer.Capacity = _bufSize;
+ }
+
+ var got = await _inputBuffer.ReadAsync(buffer, offset, length, cancellationToken);
+ if (got > 0)
+ {
+ return got;
+ }
+
+ _inputBuffer.Seek(0, SeekOrigin.Begin);
+ _inputBuffer.SetLength(_inputBuffer.Capacity);
+
+ ArraySegment<byte> bufSegment;
+ _inputBuffer.TryGetBuffer(out bufSegment);
+
+ // investigate
+ var filled = await _transport.ReadAsync(bufSegment.Array, 0, (int) _inputBuffer.Length, cancellationToken);
+ _inputBuffer.SetLength(filled);
+
+ if (filled == 0)
+ {
+ return 0;
+ }
+
+ return await ReadAsync(buffer, offset, length, cancellationToken);
+ }
+
+ public override async Task WriteAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken)
+ {
+ CheckNotDisposed();
+
+ ValidateBufferArgs(buffer, offset, length);
+
+ if (!IsOpen)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.NotOpen);
+ }
+
+ // Relative offset from "off" argument
+ var writtenCount = 0;
+ if (_outputBuffer.Length > 0)
+ {
+ var capa = (int) (_outputBuffer.Capacity - _outputBuffer.Length);
+ var writeSize = capa <= length ? capa : length;
+ await _outputBuffer.WriteAsync(buffer, offset, writeSize, cancellationToken);
+
+ writtenCount += writeSize;
+ if (writeSize == capa)
+ {
+ //ArraySegment<byte> bufSegment;
+ //_outputBuffer.TryGetBuffer(out bufSegment);
+ var data = _outputBuffer.ToArray();
+ //await _transport.WriteAsync(bufSegment.Array, cancellationToken);
+ await _transport.WriteAsync(data, cancellationToken);
+ _outputBuffer.SetLength(0);
+ }
+ }
+
+ while (length - writtenCount >= _bufSize)
+ {
+ await _transport.WriteAsync(buffer, offset + writtenCount, _bufSize, cancellationToken);
+ writtenCount += _bufSize;
+ }
+
+ var remain = length - writtenCount;
+ if (remain > 0)
+ {
+ if (_outputBuffer.Capacity < _bufSize)
+ {
+ _outputBuffer.Capacity = _bufSize;
+ }
+ await _outputBuffer.WriteAsync(buffer, offset + writtenCount, remain, cancellationToken);
+ }
+ }
+
+ public override async Task FlushAsync(CancellationToken cancellationToken)
+ {
+ CheckNotDisposed();
+
+ if (!IsOpen)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.NotOpen);
+ }
+
+ if (_outputBuffer.Length > 0)
+ {
+ //ArraySegment<byte> bufSegment;
+ var data = _outputBuffer.ToArray(); // TryGetBuffer(out bufSegment);
+
+ await _transport.WriteAsync(data /*bufSegment.Array*/, cancellationToken);
+ _outputBuffer.SetLength(0);
+ }
+
+ await _transport.FlushAsync(cancellationToken);
+ }
+
+ private void CheckNotDisposed()
+ {
+ if (_isDisposed)
+ {
+ throw new ObjectDisposedException(nameof(_transport));
+ }
+ }
+
+ // IDisposable
+ protected override void Dispose(bool disposing)
+ {
+ if (!_isDisposed)
+ {
+ if (disposing)
+ {
+ _inputBuffer.Dispose();
+ _outputBuffer.Dispose();
+ }
+ }
+ _isDisposed = true;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Transports/Client/TFramedClientTransport.cs b/lib/netcore/Thrift/Transports/Client/TFramedClientTransport.cs
new file mode 100644
index 000000000..514c1a6e6
--- /dev/null
+++ b/lib/netcore/Thrift/Transports/Client/TFramedClientTransport.cs
@@ -0,0 +1,207 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Thrift.Transports.Client
+{
+ //TODO: check for correct implementation
+
+ // ReSharper disable once InconsistentNaming
+ public class TFramedClientTransport : TClientTransport
+ {
+ private const int HeaderSize = 4;
+ private readonly byte[] _headerBuf = new byte[HeaderSize];
+ private readonly MemoryStream _readBuffer = new MemoryStream(1024);
+ private readonly TClientTransport _transport;
+ private readonly MemoryStream _writeBuffer = new MemoryStream(1024);
+
+ private bool _isDisposed;
+
+ public TFramedClientTransport(TClientTransport transport)
+ {
+ throw new NotImplementedException("TFramedClientTransport is not fully ready for usage");
+
+ if (transport == null)
+ {
+ throw new ArgumentNullException(nameof(transport));
+ }
+
+ _transport = transport;
+
+ InitWriteBuffer();
+ }
+
+ public override bool IsOpen => !_isDisposed && _transport.IsOpen;
+
+ public override async Task OpenAsync(CancellationToken cancellationToken)
+ {
+ CheckNotDisposed();
+
+ await _transport.OpenAsync(cancellationToken);
+ }
+
+ public override void Close()
+ {
+ CheckNotDisposed();
+
+ _transport.Close();
+ }
+
+ public override async Task<int> ReadAsync(byte[] buffer, int offset, int length,
+ CancellationToken cancellationToken)
+ {
+ CheckNotDisposed();
+
+ ValidateBufferArgs(buffer, offset, length);
+
+ if (!IsOpen)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.NotOpen);
+ }
+
+ var got = await _readBuffer.ReadAsync(buffer, offset, length, cancellationToken);
+ if (got > 0)
+ {
+ return got;
+ }
+
+ // Read another frame of data
+ await ReadFrameAsync(cancellationToken);
+
+ return await _readBuffer.ReadAsync(buffer, offset, length, cancellationToken);
+ }
+
+ private async Task ReadFrameAsync(CancellationToken cancellationToken)
+ {
+ await _transport.ReadAllAsync(_headerBuf, 0, HeaderSize, cancellationToken);
+
+ var size = DecodeFrameSize(_headerBuf);
+
+ _readBuffer.SetLength(size);
+ _readBuffer.Seek(0, SeekOrigin.Begin);
+
+ ArraySegment<byte> bufSegment;
+ _readBuffer.TryGetBuffer(out bufSegment);
+
+ var buff = bufSegment.Array;
+
+ await _transport.ReadAllAsync(buff, 0, size, cancellationToken);
+ }
+
+ public override async Task WriteAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken)
+ {
+ CheckNotDisposed();
+
+ ValidateBufferArgs(buffer, offset, length);
+
+ if (!IsOpen)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.NotOpen);
+ }
+
+ if (_writeBuffer.Length + length > int.MaxValue)
+ {
+ await FlushAsync(cancellationToken);
+ }
+
+ await _writeBuffer.WriteAsync(buffer, offset, length, cancellationToken);
+ }
+
+ public override async Task FlushAsync(CancellationToken cancellationToken)
+ {
+ CheckNotDisposed();
+
+ if (!IsOpen)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.NotOpen);
+ }
+
+ //ArraySegment<byte> bufSegment;
+ //_writeBuffer.TryGetBuffer(out bufSegment);
+ //var buf = bufSegment.Array;
+ var buf = _writeBuffer.ToArray();
+
+ //var len = (int)_writeBuffer.Length;
+ var dataLen = (int) _writeBuffer.Length - HeaderSize;
+ if (dataLen < 0)
+ {
+ throw new InvalidOperationException(); // logic error actually
+ }
+
+ // Inject message header into the reserved buffer space
+ EncodeFrameSize(dataLen, buf);
+
+ // Send the entire message at once
+ await _transport.WriteAsync(buf, cancellationToken);
+
+ InitWriteBuffer();
+
+ await _transport.FlushAsync(cancellationToken);
+ }
+
+ private void InitWriteBuffer()
+ {
+ // Reserve space for message header to be put right before sending it out
+ _writeBuffer.SetLength(HeaderSize);
+ _writeBuffer.Seek(0, SeekOrigin.End);
+ }
+
+ private static void EncodeFrameSize(int frameSize, byte[] buf)
+ {
+ buf[0] = (byte) (0xff & (frameSize >> 24));
+ buf[1] = (byte) (0xff & (frameSize >> 16));
+ buf[2] = (byte) (0xff & (frameSize >> 8));
+ buf[3] = (byte) (0xff & (frameSize));
+ }
+
+ private static int DecodeFrameSize(byte[] buf)
+ {
+ return
+ ((buf[0] & 0xff) << 24) |
+ ((buf[1] & 0xff) << 16) |
+ ((buf[2] & 0xff) << 8) |
+ (buf[3] & 0xff);
+ }
+
+
+ private void CheckNotDisposed()
+ {
+ if (_isDisposed)
+ {
+ throw new ObjectDisposedException("TFramedClientTransport");
+ }
+ }
+
+ // IDisposable
+ protected override void Dispose(bool disposing)
+ {
+ if (!_isDisposed)
+ {
+ if (disposing)
+ {
+ _readBuffer.Dispose();
+ _writeBuffer.Dispose();
+ }
+ }
+ _isDisposed = true;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Transports/Client/THttpClientTransport.cs b/lib/netcore/Thrift/Transports/Client/THttpClientTransport.cs
new file mode 100644
index 000000000..bc36bb3b4
--- /dev/null
+++ b/lib/netcore/Thrift/Transports/Client/THttpClientTransport.cs
@@ -0,0 +1,228 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Thrift.Transports.Client
+{
+ // ReSharper disable once InconsistentNaming
+ public class THttpClientTransport : TClientTransport
+ {
+ private readonly X509Certificate[] _certificates;
+ private readonly Uri _uri;
+
+ // Timeouts in milliseconds
+ private int _connectTimeout = 30000;
+ private HttpClient _httpClient;
+ private Stream _inputStream;
+
+ private bool _isDisposed;
+ private MemoryStream _outputStream = new MemoryStream();
+
+ public THttpClientTransport(Uri u, IDictionary<string, string> customHeaders)
+ : this(u, Enumerable.Empty<X509Certificate>(), customHeaders)
+ {
+ }
+
+ public THttpClientTransport(Uri u, IEnumerable<X509Certificate> certificates,
+ IDictionary<string, string> customHeaders)
+ {
+ _uri = u;
+ _certificates = (certificates ?? Enumerable.Empty<X509Certificate>()).ToArray();
+ CustomHeaders = customHeaders;
+
+ // due to current bug with performance of Dispose in netcore https://github.com/dotnet/corefx/issues/8809
+ // this can be switched to default way (create client->use->dispose per flush) later
+ _httpClient = CreateClient();
+ }
+
+ public IDictionary<string, string> CustomHeaders { get; }
+
+ public int ConnectTimeout
+ {
+ set { _connectTimeout = value; }
+ }
+
+ public override bool IsOpen => true;
+
+ public override async Task OpenAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override void Close()
+ {
+ if (_inputStream != null)
+ {
+ _inputStream.Dispose();
+ _inputStream = null;
+ }
+
+ if (_outputStream != null)
+ {
+ _outputStream.Dispose();
+ _outputStream = null;
+ }
+
+ if (_httpClient != null)
+ {
+ _httpClient.Dispose();
+ _httpClient = null;
+ }
+ }
+
+ public override async Task<int> ReadAsync(byte[] buffer, int offset, int length,
+ CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<int>(cancellationToken);
+ }
+
+ if (_inputStream == null)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.NotOpen, "No request has been sent");
+ }
+
+ try
+ {
+ var ret = await _inputStream.ReadAsync(buffer, offset, length, cancellationToken);
+
+ if (ret == -1)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.EndOfFile, "No more data available");
+ }
+
+ return ret;
+ }
+ catch (IOException iox)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.Unknown, iox.ToString());
+ }
+ }
+
+ public override async Task WriteAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+
+ await _outputStream.WriteAsync(buffer, offset, length, cancellationToken);
+ }
+
+ private HttpClient CreateClient()
+ {
+ var handler = new HttpClientHandler();
+ handler.ClientCertificates.AddRange(_certificates);
+
+ var httpClient = new HttpClient(handler);
+
+ if (_connectTimeout > 0)
+ {
+ httpClient.Timeout = TimeSpan.FromSeconds(_connectTimeout);
+ }
+
+ httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-thrift"));
+ httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("THttpClientTransport", "1.0.0"));
+
+ if (CustomHeaders != null)
+ {
+ foreach (var item in CustomHeaders)
+ {
+ httpClient.DefaultRequestHeaders.Add(item.Key, item.Value);
+ }
+ }
+
+ return httpClient;
+ }
+
+ public override async Task FlushAsync(CancellationToken cancellationToken)
+ {
+ try
+ {
+ try
+ {
+ if (_outputStream.CanSeek)
+ {
+ _outputStream.Seek(0, SeekOrigin.Begin);
+ }
+
+ using (var outStream = new StreamContent(_outputStream))
+ {
+ var msg = await _httpClient.PostAsync(_uri, outStream, cancellationToken);
+
+ msg.EnsureSuccessStatusCode();
+
+ if (_inputStream != null)
+ {
+ _inputStream.Dispose();
+ _inputStream = null;
+ }
+
+ _inputStream = await msg.Content.ReadAsStreamAsync();
+ if (_inputStream.CanSeek)
+ {
+ _inputStream.Seek(0, SeekOrigin.Begin);
+ }
+ }
+ }
+ catch (IOException iox)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.Unknown, iox.ToString());
+ }
+ catch (WebException wx)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.Unknown,
+ "Couldn't connect to server: " + wx);
+ }
+ }
+ finally
+ {
+ _outputStream = new MemoryStream();
+ }
+ }
+
+ // IDisposable
+ protected override void Dispose(bool disposing)
+ {
+ if (!_isDisposed)
+ {
+ if (disposing)
+ {
+ _inputStream?.Dispose();
+ _outputStream?.Dispose();
+ _httpClient?.Dispose();
+ }
+ }
+ _isDisposed = true;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Transports/Client/TMemoryBufferClientTransport.cs b/lib/netcore/Thrift/Transports/Client/TMemoryBufferClientTransport.cs
new file mode 100644
index 000000000..46a55a64a
--- /dev/null
+++ b/lib/netcore/Thrift/Transports/Client/TMemoryBufferClientTransport.cs
@@ -0,0 +1,97 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Thrift.Transports.Client
+{
+ // ReSharper disable once InconsistentNaming
+ public class TMemoryBufferClientTransport : TClientTransport
+ {
+ private readonly MemoryStream _byteStream;
+ private bool _isDisposed;
+
+ public TMemoryBufferClientTransport()
+ {
+ _byteStream = new MemoryStream();
+ }
+
+ public TMemoryBufferClientTransport(byte[] buf)
+ {
+ _byteStream = new MemoryStream(buf);
+ }
+
+ public override bool IsOpen => true;
+
+ public override async Task OpenAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override void Close()
+ {
+ /** do nothing **/
+ }
+
+ public override async Task<int> ReadAsync(byte[] buffer, int offset, int length,
+ CancellationToken cancellationToken)
+ {
+ return await _byteStream.ReadAsync(buffer, offset, length, cancellationToken);
+ }
+
+ public override async Task WriteAsync(byte[] buffer, CancellationToken cancellationToken)
+ {
+ await _byteStream.WriteAsync(buffer, 0, buffer.Length, cancellationToken);
+ }
+
+ public override async Task WriteAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken)
+ {
+ await _byteStream.WriteAsync(buffer, offset, length, cancellationToken);
+ }
+
+ public override async Task FlushAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public byte[] GetBuffer()
+ {
+ return _byteStream.ToArray();
+ }
+
+ // IDisposable
+ protected override void Dispose(bool disposing)
+ {
+ if (!_isDisposed)
+ {
+ if (disposing)
+ {
+ _byteStream?.Dispose();
+ }
+ }
+ _isDisposed = true;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Transports/Client/TNamedPipeClientTransport.cs b/lib/netcore/Thrift/Transports/Client/TNamedPipeClientTransport.cs
new file mode 100644
index 000000000..f5e4baf4a
--- /dev/null
+++ b/lib/netcore/Thrift/Transports/Client/TNamedPipeClientTransport.cs
@@ -0,0 +1,95 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System.IO.Pipes;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Thrift.Transports.Client
+{
+ // ReSharper disable once InconsistentNaming
+ public class TNamedPipeClientTransport : TClientTransport
+ {
+ private NamedPipeClientStream _client;
+
+ public TNamedPipeClientTransport(string pipe) : this(".", pipe)
+ {
+ }
+
+ public TNamedPipeClientTransport(string server, string pipe)
+ {
+ var serverName = string.IsNullOrWhiteSpace(server) ? server : ".";
+
+ _client = new NamedPipeClientStream(serverName, pipe, PipeDirection.InOut, PipeOptions.None);
+ }
+
+ public override bool IsOpen => _client != null && _client.IsConnected;
+
+ public override async Task OpenAsync(CancellationToken cancellationToken)
+ {
+ if (IsOpen)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.AlreadyOpen);
+ }
+
+ await _client.ConnectAsync(cancellationToken);
+ }
+
+ public override void Close()
+ {
+ if (_client != null)
+ {
+ _client.Dispose();
+ _client = null;
+ }
+ }
+
+ public override async Task<int> ReadAsync(byte[] buffer, int offset, int length,
+ CancellationToken cancellationToken)
+ {
+ if (_client == null)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.NotOpen);
+ }
+
+ return await _client.ReadAsync(buffer, offset, length, cancellationToken);
+ }
+
+ public override async Task WriteAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken)
+ {
+ if (_client == null)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.NotOpen);
+ }
+
+ await _client.WriteAsync(buffer, offset, length, cancellationToken);
+ }
+
+ public override async Task FlushAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ _client.Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Transports/Client/TSocketClientTransport.cs b/lib/netcore/Thrift/Transports/Client/TSocketClientTransport.cs
new file mode 100644
index 000000000..a44efe677
--- /dev/null
+++ b/lib/netcore/Thrift/Transports/Client/TSocketClientTransport.cs
@@ -0,0 +1,144 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Thrift.Transports.Client
+{
+ // ReSharper disable once InconsistentNaming
+ public class TSocketClientTransport : TStreamClientTransport
+ {
+ private bool _isDisposed;
+
+ public TSocketClientTransport(TcpClient client)
+ {
+ if (client == null)
+ {
+ throw new ArgumentNullException(nameof(client));
+ }
+
+ TcpClient = client;
+
+ if (IsOpen)
+ {
+ InputStream = client.GetStream();
+ OutputStream = client.GetStream();
+ }
+ }
+
+ public TSocketClientTransport(IPAddress host, int port)
+ : this(host, port, 0)
+ {
+ }
+
+ public TSocketClientTransport(IPAddress host, int port, int timeout)
+ {
+ Host = host;
+ Port = port;
+
+ TcpClient = new TcpClient();
+ TcpClient.ReceiveTimeout = TcpClient.SendTimeout = timeout;
+ TcpClient.Client.NoDelay = true;
+ }
+
+ public TcpClient TcpClient { get; private set; }
+ public IPAddress Host { get; }
+ public int Port { get; }
+
+ public int Timeout
+ {
+ set
+ {
+ if (TcpClient != null)
+ {
+ TcpClient.ReceiveTimeout = TcpClient.SendTimeout = value;
+ }
+ }
+ }
+
+ public override bool IsOpen
+ {
+ get
+ {
+ if (TcpClient == null)
+ {
+ return false;
+ }
+
+ return TcpClient.Connected;
+ }
+ }
+
+ public override async Task OpenAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+
+ if (IsOpen)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.AlreadyOpen, "Socket already connected");
+ }
+
+ if (Port <= 0)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open without port");
+ }
+
+ if (TcpClient == null)
+ {
+ throw new InvalidOperationException("Invalid or not initialized tcp client");
+ }
+
+ await TcpClient.ConnectAsync(Host, Port);
+
+ InputStream = TcpClient.GetStream();
+ OutputStream = TcpClient.GetStream();
+ }
+
+ public override void Close()
+ {
+ base.Close();
+
+ if (TcpClient != null)
+ {
+ TcpClient.Dispose();
+ TcpClient = null;
+ }
+ }
+
+ // IDisposable
+ protected override void Dispose(bool disposing)
+ {
+ if (!_isDisposed)
+ {
+ if (disposing)
+ {
+ TcpClient?.Dispose();
+
+ base.Dispose(disposing);
+ }
+ }
+ _isDisposed = true;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Transports/Client/TStreamClientTransport.cs b/lib/netcore/Thrift/Transports/Client/TStreamClientTransport.cs
new file mode 100644
index 000000000..f7164f045
--- /dev/null
+++ b/lib/netcore/Thrift/Transports/Client/TStreamClientTransport.cs
@@ -0,0 +1,110 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Thrift.Transports.Client
+{
+ // ReSharper disable once InconsistentNaming
+ public class TStreamClientTransport : TClientTransport
+ {
+ private bool _isDisposed;
+
+ protected TStreamClientTransport()
+ {
+ }
+
+ public TStreamClientTransport(Stream inputStream, Stream outputStream)
+ {
+ InputStream = inputStream;
+ OutputStream = outputStream;
+ }
+
+ protected Stream OutputStream { get; set; }
+
+ protected Stream InputStream { get; set; }
+
+ public override bool IsOpen => true;
+
+ public override async Task OpenAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override void Close()
+ {
+ if (InputStream != null)
+ {
+ InputStream.Dispose();
+ InputStream = null;
+ }
+
+ if (OutputStream != null)
+ {
+ OutputStream.Dispose();
+ OutputStream = null;
+ }
+ }
+
+ public override async Task<int> ReadAsync(byte[] buffer, int offset, int length,
+ CancellationToken cancellationToken)
+ {
+ if (InputStream == null)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.NotOpen,
+ "Cannot read from null inputstream");
+ }
+
+ return await InputStream.ReadAsync(buffer, offset, length, cancellationToken);
+ }
+
+ public override async Task WriteAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken)
+ {
+ if (OutputStream == null)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.NotOpen,
+ "Cannot read from null inputstream");
+ }
+
+ await OutputStream.WriteAsync(buffer, offset, length, cancellationToken);
+ }
+
+ public override async Task FlushAsync(CancellationToken cancellationToken)
+ {
+ await OutputStream.FlushAsync(cancellationToken);
+ }
+
+ // IDisposable
+ protected override void Dispose(bool disposing)
+ {
+ if (!_isDisposed)
+ {
+ if (disposing)
+ {
+ InputStream?.Dispose();
+ OutputStream?.Dispose();
+ }
+ }
+ _isDisposed = true;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Transports/Client/TTlsSocketClientTransport.cs b/lib/netcore/Thrift/Transports/Client/TTlsSocketClientTransport.cs
new file mode 100644
index 000000000..a21977b20
--- /dev/null
+++ b/lib/netcore/Thrift/Transports/Client/TTlsSocketClientTransport.cs
@@ -0,0 +1,236 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Net;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Security.Authentication;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Thrift.Transports.Client
+{
+ //TODO: check for correct work
+
+ // ReSharper disable once InconsistentNaming
+ public class TTlsSocketClientTransport : TStreamClientTransport
+ {
+ private readonly X509Certificate2 _certificate;
+ private readonly RemoteCertificateValidationCallback _certValidator;
+ private readonly IPAddress _host;
+ private readonly bool _isServer;
+ private readonly LocalCertificateSelectionCallback _localCertificateSelectionCallback;
+ private readonly int _port;
+ private readonly SslProtocols _sslProtocols;
+ private TcpClient _client;
+ private SslStream _secureStream;
+ private int _timeout;
+
+ public TTlsSocketClientTransport(TcpClient client, X509Certificate2 certificate, bool isServer = false,
+ RemoteCertificateValidationCallback certValidator = null,
+ LocalCertificateSelectionCallback localCertificateSelectionCallback = null,
+ SslProtocols sslProtocols = SslProtocols.Tls12)
+ {
+ _client = client;
+ _certificate = certificate;
+ _certValidator = certValidator;
+ _localCertificateSelectionCallback = localCertificateSelectionCallback;
+ _sslProtocols = sslProtocols;
+ _isServer = isServer;
+
+ if (isServer && certificate == null)
+ {
+ throw new ArgumentException("TTlsSocketClientTransport needs certificate to be used for server",
+ "certificate");
+ }
+
+ if (IsOpen)
+ {
+ InputStream = client.GetStream();
+ OutputStream = client.GetStream();
+ }
+ }
+
+ public TTlsSocketClientTransport(IPAddress host, int port, string certificatePath,
+ RemoteCertificateValidationCallback certValidator = null,
+ LocalCertificateSelectionCallback localCertificateSelectionCallback = null,
+ SslProtocols sslProtocols = SslProtocols.Tls12)
+ : this(host, port, 0,
+ new X509Certificate2(certificatePath),
+ certValidator,
+ localCertificateSelectionCallback,
+ sslProtocols)
+ {
+ }
+
+ public TTlsSocketClientTransport(IPAddress host, int port,
+ X509Certificate2 certificate = null,
+ RemoteCertificateValidationCallback certValidator = null,
+ LocalCertificateSelectionCallback localCertificateSelectionCallback = null,
+ SslProtocols sslProtocols = SslProtocols.Tls12)
+ : this(host, port, 0,
+ certificate,
+ certValidator,
+ localCertificateSelectionCallback,
+ sslProtocols)
+ {
+ }
+
+ public TTlsSocketClientTransport(IPAddress host, int port, int timeout,
+ X509Certificate2 certificate,
+ RemoteCertificateValidationCallback certValidator = null,
+ LocalCertificateSelectionCallback localCertificateSelectionCallback = null,
+ SslProtocols sslProtocols = SslProtocols.Tls12)
+ {
+ _host = host;
+ _port = port;
+ _timeout = timeout;
+ _certificate = certificate;
+ _certValidator = certValidator;
+ _localCertificateSelectionCallback = localCertificateSelectionCallback;
+ _sslProtocols = sslProtocols;
+
+ InitSocket();
+ }
+
+ public int Timeout
+ {
+ set { _client.ReceiveTimeout = _client.SendTimeout = _timeout = value; }
+ }
+
+ public TcpClient TcpClient => _client;
+
+ public IPAddress Host => _host;
+
+ public int Port => _port;
+
+ public override bool IsOpen
+ {
+ get
+ {
+ if (_client == null)
+ {
+ return false;
+ }
+
+ return _client.Connected;
+ }
+ }
+
+ private void InitSocket()
+ {
+ _client = new TcpClient();
+ _client.ReceiveTimeout = _client.SendTimeout = _timeout;
+ _client.Client.NoDelay = true;
+ }
+
+ private bool DefaultCertificateValidator(object sender, X509Certificate certificate, X509Chain chain,
+ SslPolicyErrors sslValidationErrors)
+ {
+ return sslValidationErrors == SslPolicyErrors.None;
+ }
+
+ public override async Task OpenAsync(CancellationToken cancellationToken)
+ {
+ if (IsOpen)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.AlreadyOpen, "Socket already connected");
+ }
+
+ if (_host == null)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open null host");
+ }
+
+ if (_port <= 0)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open without port");
+ }
+
+ if (_client == null)
+ {
+ InitSocket();
+ }
+
+ if (_client != null)
+ {
+ await _client.ConnectAsync(_host, _port);
+ await SetupTlsAsync();
+ }
+ }
+
+ public async Task SetupTlsAsync()
+ {
+ var validator = _certValidator ?? DefaultCertificateValidator;
+
+ if (_localCertificateSelectionCallback != null)
+ {
+ _secureStream = new SslStream(_client.GetStream(), false, validator, _localCertificateSelectionCallback);
+ }
+ else
+ {
+ _secureStream = new SslStream(_client.GetStream(), false, validator);
+ }
+
+ try
+ {
+ if (_isServer)
+ {
+ // Server authentication
+ await
+ _secureStream.AuthenticateAsServerAsync(_certificate, _certValidator != null, _sslProtocols,
+ true);
+ }
+ else
+ {
+ // Client authentication
+ var certs = _certificate != null
+ ? new X509CertificateCollection {_certificate}
+ : new X509CertificateCollection();
+
+ await _secureStream.AuthenticateAsClientAsync(_host.ToString(), certs, _sslProtocols, true);
+ }
+ }
+ catch (Exception)
+ {
+ Close();
+ throw;
+ }
+
+ InputStream = _secureStream;
+ OutputStream = _secureStream;
+ }
+
+ public override void Close()
+ {
+ base.Close();
+ if (_client != null)
+ {
+ _client.Dispose();
+ _client = null;
+ }
+
+ if (_secureStream != null)
+ {
+ _secureStream.Dispose();
+ _secureStream = null;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Transports/Server/THttpServerTransport.cs b/lib/netcore/Thrift/Transports/Server/THttpServerTransport.cs
new file mode 100644
index 000000000..607374135
--- /dev/null
+++ b/lib/netcore/Thrift/Transports/Server/THttpServerTransport.cs
@@ -0,0 +1,113 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Thrift.Protocols;
+using Thrift.Transports.Client;
+
+namespace Thrift.Transports.Server
+{
+ // ReSharper disable once InconsistentNaming
+ public class THttpServerTransport
+ {
+ protected const string ContentType = "application/x-thrift";
+ private readonly ILogger _logger;
+ private readonly RequestDelegate _next;
+ protected Encoding Encoding = Encoding.UTF8;
+
+ protected ITProtocolFactory InputProtocolFactory;
+ protected ITProtocolFactory OutputProtocolFactory;
+
+ protected ITAsyncProcessor Processor;
+
+ public THttpServerTransport(ITAsyncProcessor processor, RequestDelegate next, ILoggerFactory loggerFactory)
+ : this(processor, new TBinaryProtocol.Factory(), next, loggerFactory)
+ {
+ }
+
+ public THttpServerTransport(ITAsyncProcessor processor, ITProtocolFactory protocolFactory, RequestDelegate next,
+ ILoggerFactory loggerFactory)
+ : this(processor, protocolFactory, protocolFactory, next, loggerFactory)
+ {
+ }
+
+ public THttpServerTransport(ITAsyncProcessor processor, ITProtocolFactory inputProtocolFactory,
+ ITProtocolFactory outputProtocolFactory, RequestDelegate next, ILoggerFactory loggerFactory)
+ {
+ if (processor == null)
+ {
+ throw new ArgumentNullException(nameof(processor));
+ }
+
+ if (inputProtocolFactory == null)
+ {
+ throw new ArgumentNullException(nameof(inputProtocolFactory));
+ }
+
+ if (outputProtocolFactory == null)
+ {
+ throw new ArgumentNullException(nameof(outputProtocolFactory));
+ }
+
+ if (loggerFactory == null)
+ {
+ throw new ArgumentNullException(nameof(loggerFactory));
+ }
+
+ Processor = processor;
+ InputProtocolFactory = inputProtocolFactory;
+ OutputProtocolFactory = outputProtocolFactory;
+
+ _next = next;
+ _logger = loggerFactory.CreateLogger<THttpServerTransport>();
+ }
+
+ public async Task Invoke(HttpContext context)
+ {
+ context.Response.ContentType = ContentType;
+ await ProcessRequestAsync(context, context.RequestAborted); //TODO: check for correct logic
+ }
+
+ public async Task ProcessRequestAsync(HttpContext context, CancellationToken cancellationToken)
+ {
+ var transport = new TStreamClientTransport(context.Request.Body, context.Response.Body);
+
+ try
+ {
+ var input = InputProtocolFactory.GetProtocol(transport);
+ var output = OutputProtocolFactory.GetProtocol(transport);
+
+ while (await Processor.ProcessAsync(input, output, cancellationToken))
+ {
+ }
+ }
+ catch (TTransportException)
+ {
+ // Client died, just move on
+ }
+ finally
+ {
+ transport.Close();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Transports/Server/TNamedPipeServerTransport.cs b/lib/netcore/Thrift/Transports/Server/TNamedPipeServerTransport.cs
new file mode 100644
index 000000000..01195d4a4
--- /dev/null
+++ b/lib/netcore/Thrift/Transports/Server/TNamedPipeServerTransport.cs
@@ -0,0 +1,191 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.IO.Pipes;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Thrift.Transports.Server
+{
+ // ReSharper disable once InconsistentNaming
+ public class TNamedPipeServerTransport : TServerTransport
+ {
+ /// <summary>
+ /// This is the address of the Pipe on the localhost.
+ /// </summary>
+ private readonly string _pipeAddress;
+
+ private bool _asyncMode = true;
+ private volatile bool _isPending = true;
+
+ private NamedPipeServerStream _stream = null;
+
+ public TNamedPipeServerTransport(string pipeAddress)
+ {
+ _pipeAddress = pipeAddress;
+ }
+
+ public override void Listen()
+ {
+ // nothing to do here
+ }
+
+ public override void Close()
+ {
+ if (_stream != null)
+ {
+ try
+ {
+ //TODO: check for disconection
+ _stream.Disconnect();
+ _stream.Dispose();
+ }
+ finally
+ {
+ _stream = null;
+ _isPending = false;
+ }
+ }
+ }
+
+ public override bool IsClientPending()
+ {
+ return _isPending;
+ }
+
+ private void EnsurePipeInstance()
+ {
+ if (_stream == null)
+ {
+ var direction = PipeDirection.InOut;
+ var maxconn = 254;
+ var mode = PipeTransmissionMode.Byte;
+ var options = _asyncMode ? PipeOptions.Asynchronous : PipeOptions.None;
+ var inbuf = 4096;
+ var outbuf = 4096;
+ // TODO: security
+
+ try
+ {
+ _stream = new NamedPipeServerStream(_pipeAddress, direction, maxconn, mode, options, inbuf, outbuf);
+ }
+ catch (NotImplementedException) // Mono still does not support async, fallback to sync
+ {
+ if (_asyncMode)
+ {
+ options &= (~PipeOptions.Asynchronous);
+ _stream = new NamedPipeServerStream(_pipeAddress, direction, maxconn, mode, options, inbuf,
+ outbuf);
+ _asyncMode = false;
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+ }
+
+ protected override async Task<TClientTransport> AcceptImplementationAsync(CancellationToken cancellationToken)
+ {
+ try
+ {
+ EnsurePipeInstance();
+
+ await _stream.WaitForConnectionAsync(cancellationToken);
+
+ var trans = new ServerTransport(_stream);
+ _stream = null; // pass ownership to ServerTransport
+
+ //_isPending = false;
+
+ return trans;
+ }
+ catch (TTransportException)
+ {
+ Close();
+ throw;
+ }
+ catch (Exception e)
+ {
+ Close();
+ throw new TTransportException(TTransportException.ExceptionType.NotOpen, e.Message);
+ }
+ }
+
+ private class ServerTransport : TClientTransport
+ {
+ private readonly NamedPipeServerStream _stream;
+
+ public ServerTransport(NamedPipeServerStream stream)
+ {
+ _stream = stream;
+ }
+
+ public override bool IsOpen => _stream != null && _stream.IsConnected;
+
+ public override async Task OpenAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ public override void Close()
+ {
+ _stream?.Dispose();
+ }
+
+ public override async Task<int> ReadAsync(byte[] buffer, int offset, int length,
+ CancellationToken cancellationToken)
+ {
+ if (_stream == null)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.NotOpen);
+ }
+
+ return await _stream.ReadAsync(buffer, offset, length, cancellationToken);
+ }
+
+ public override async Task WriteAsync(byte[] buffer, int offset, int length,
+ CancellationToken cancellationToken)
+ {
+ if (_stream == null)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.NotOpen);
+ }
+
+ await _stream.WriteAsync(buffer, offset, length, cancellationToken);
+ }
+
+ public override async Task FlushAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken);
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ _stream?.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Transports/Server/TServerSocketTransport.cs b/lib/netcore/Thrift/Transports/Server/TServerSocketTransport.cs
new file mode 100644
index 000000000..af154ef6f
--- /dev/null
+++ b/lib/netcore/Thrift/Transports/Server/TServerSocketTransport.cs
@@ -0,0 +1,162 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+using Thrift.Transports.Client;
+
+namespace Thrift.Transports.Server
+{
+ // ReSharper disable once InconsistentNaming
+ public class TServerSocketTransport : TServerTransport
+ {
+ private readonly int _clientTimeout;
+ private readonly int _port;
+ private readonly bool _useBufferedSockets;
+ private TcpListener _server;
+
+ public TServerSocketTransport(TcpListener listener)
+ : this(listener, 0)
+ {
+ }
+
+ public TServerSocketTransport(TcpListener listener, int clientTimeout)
+ {
+ _server = listener;
+ _clientTimeout = clientTimeout;
+ }
+
+ public TServerSocketTransport(int port)
+ : this(port, 0)
+ {
+ }
+
+ public TServerSocketTransport(int port, int clientTimeout)
+ : this(port, clientTimeout, false)
+ {
+ }
+
+ public TServerSocketTransport(int port, int clientTimeout, bool useBufferedSockets)
+ {
+ _port = port;
+ _clientTimeout = clientTimeout;
+ _useBufferedSockets = useBufferedSockets;
+ try
+ {
+ // Make server socket
+ _server = new TcpListener(IPAddress.Any, _port);
+ _server.Server.NoDelay = true;
+ }
+ catch (Exception)
+ {
+ _server = null;
+ throw new TTransportException("Could not create ServerSocket on port " + port + ".");
+ }
+ }
+
+ public override void Listen()
+ {
+ // Make sure not to block on accept
+ if (_server != null)
+ {
+ try
+ {
+ _server.Start();
+ }
+ catch (SocketException sx)
+ {
+ throw new TTransportException("Could not accept on listening socket: " + sx.Message);
+ }
+ }
+ }
+
+ public override bool IsClientPending()
+ {
+ return _server.Pending();
+ }
+
+ protected override async Task<TClientTransport> AcceptImplementationAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<TClientTransport>(cancellationToken);
+ }
+
+ if (_server == null)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.NotOpen, "No underlying server socket.");
+ }
+
+ try
+ {
+ TSocketClientTransport tSocketTransport = null;
+ var tcpClient = await _server.AcceptTcpClientAsync();
+
+ try
+ {
+ tSocketTransport = new TSocketClientTransport(tcpClient)
+ {
+ Timeout = _clientTimeout
+ };
+
+ if (_useBufferedSockets)
+ {
+ return new TBufferedClientTransport(tSocketTransport);
+ }
+
+ return tSocketTransport;
+ }
+ catch (Exception)
+ {
+ if (tSocketTransport != null)
+ {
+ tSocketTransport.Dispose();
+ }
+ else // Otherwise, clean it up ourselves.
+ {
+ ((IDisposable) tcpClient).Dispose();
+ }
+
+ throw;
+ }
+ }
+ catch (Exception ex)
+ {
+ throw new TTransportException(ex.ToString());
+ }
+ }
+
+ public override void Close()
+ {
+ if (_server != null)
+ {
+ try
+ {
+ _server.Stop();
+ }
+ catch (Exception ex)
+ {
+ throw new TTransportException("WARNING: Could not close server socket: " + ex);
+ }
+ _server = null;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Transports/Server/TTlsServerSocketTransport.cs b/lib/netcore/Thrift/Transports/Server/TTlsServerSocketTransport.cs
new file mode 100644
index 000000000..49abdac86
--- /dev/null
+++ b/lib/netcore/Thrift/Transports/Server/TTlsServerSocketTransport.cs
@@ -0,0 +1,156 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Net;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Security.Authentication;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
+using Thrift.Transports.Client;
+
+namespace Thrift.Transports.Server
+{
+ // ReSharper disable once InconsistentNaming
+ public class TTlsServerSocketTransport : TServerTransport
+ {
+ private readonly RemoteCertificateValidationCallback _clientCertValidator;
+ private readonly int _clientTimeout = 0;
+ private readonly LocalCertificateSelectionCallback _localCertificateSelectionCallback;
+ private readonly int _port;
+ private readonly X509Certificate2 _serverCertificate;
+ private readonly SslProtocols _sslProtocols;
+ private readonly bool _useBufferedSockets;
+ private TcpListener _server;
+
+ public TTlsServerSocketTransport(int port, X509Certificate2 certificate)
+ : this(port, false, certificate)
+ {
+ }
+
+ public TTlsServerSocketTransport(
+ int port,
+ bool useBufferedSockets,
+ X509Certificate2 certificate,
+ RemoteCertificateValidationCallback clientCertValidator = null,
+ LocalCertificateSelectionCallback localCertificateSelectionCallback = null,
+ SslProtocols sslProtocols = SslProtocols.Tls12)
+ {
+ if (!certificate.HasPrivateKey)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.Unknown,
+ "Your server-certificate needs to have a private key");
+ }
+
+ _port = port;
+ _serverCertificate = certificate;
+ _useBufferedSockets = useBufferedSockets;
+ _clientCertValidator = clientCertValidator;
+ _localCertificateSelectionCallback = localCertificateSelectionCallback;
+ _sslProtocols = sslProtocols;
+
+ try
+ {
+ // Create server socket
+ _server = new TcpListener(IPAddress.Any, _port);
+ _server.Server.NoDelay = true;
+ }
+ catch (Exception)
+ {
+ _server = null;
+ throw new TTransportException($"Could not create ServerSocket on port {port}.");
+ }
+ }
+
+ public override void Listen()
+ {
+ // Make sure accept is not blocking
+ if (_server != null)
+ {
+ try
+ {
+ _server.Start();
+ }
+ catch (SocketException sx)
+ {
+ throw new TTransportException($"Could not accept on listening socket: {sx.Message}");
+ }
+ }
+ }
+
+ public override bool IsClientPending()
+ {
+ return _server.Pending();
+ }
+
+ protected override async Task<TClientTransport> AcceptImplementationAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<TClientTransport>(cancellationToken);
+ }
+
+ if (_server == null)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.NotOpen, "No underlying server socket.");
+ }
+
+ try
+ {
+ var client = await _server.AcceptTcpClientAsync();
+ client.SendTimeout = client.ReceiveTimeout = _clientTimeout;
+
+ //wrap the client in an SSL Socket passing in the SSL cert
+ var tTlsSocket = new TTlsSocketClientTransport(client, _serverCertificate, true, _clientCertValidator,
+ _localCertificateSelectionCallback, _sslProtocols);
+
+ await tTlsSocket.SetupTlsAsync();
+
+ if (_useBufferedSockets)
+ {
+ var trans = new TBufferedClientTransport(tTlsSocket);
+ return trans;
+ }
+
+ return tTlsSocket;
+ }
+ catch (Exception ex)
+ {
+ throw new TTransportException(ex.ToString());
+ }
+ }
+
+ public override void Close()
+ {
+ if (_server != null)
+ {
+ try
+ {
+ _server.Stop();
+ }
+ catch (Exception ex)
+ {
+ throw new TTransportException($"WARNING: Could not close server socket: {ex}");
+ }
+
+ _server = null;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Transports/TClientTransport.cs b/lib/netcore/Thrift/Transports/TClientTransport.cs
new file mode 100644
index 000000000..cee0a0075
--- /dev/null
+++ b/lib/netcore/Thrift/Transports/TClientTransport.cs
@@ -0,0 +1,178 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Thrift.Transports
+{
+ //TODO: think about client info
+ // ReSharper disable once InconsistentNaming
+ public abstract class TClientTransport : IDisposable
+ {
+ private readonly byte[] _peekBuffer = new byte[1];
+ private bool _hasPeekByte;
+ public abstract bool IsOpen { get; }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ public async Task<bool> PeekAsync(CancellationToken cancellationToken)
+ {
+ //If we already have a byte read but not consumed, do nothing.
+ if (_hasPeekByte)
+ {
+ return true;
+ }
+
+ //If transport closed we can't peek.
+ if (!IsOpen)
+ {
+ return false;
+ }
+
+ //Try to read one byte. If succeeds we will need to store it for the next read.
+ try
+ {
+ var bytes = await ReadAsync(_peekBuffer, 0, 1, cancellationToken);
+ if (bytes == 0)
+ {
+ return false;
+ }
+ }
+ catch (IOException)
+ {
+ return false;
+ }
+
+ _hasPeekByte = true;
+ return true;
+ }
+
+ public virtual async Task OpenAsync()
+ {
+ await OpenAsync(CancellationToken.None);
+ }
+
+ public abstract Task OpenAsync(CancellationToken cancellationToken);
+
+ public abstract void Close();
+
+ protected static void ValidateBufferArgs(byte[] buffer, int offset, int length)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), "Buffer offset is smaller than zero.");
+ }
+
+ if (length < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length), "Buffer length is smaller than zero.");
+ }
+
+ if (offset + length > buffer.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(buffer), "Not enough data.");
+ }
+ }
+
+ public virtual async Task<int> ReadAsync(byte[] buffer, int offset, int length)
+ {
+ return await ReadAsync(buffer, offset, length, CancellationToken.None);
+ }
+
+ public abstract Task<int> ReadAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken);
+
+ public virtual async Task<int> ReadAllAsync(byte[] buffer, int offset, int length)
+ {
+ return await ReadAllAsync(buffer, offset, length, CancellationToken.None);
+ }
+
+ public virtual async Task<int> ReadAllAsync(byte[] buffer, int offset, int length,
+ CancellationToken cancellationToken)
+ {
+ ValidateBufferArgs(buffer, offset, length);
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<int>(cancellationToken);
+ }
+
+ var retrieved = 0;
+
+ //If we previously peeked a byte, we need to use that first.
+ if (_hasPeekByte)
+ {
+ buffer[offset + retrieved++] = _peekBuffer[0];
+ _hasPeekByte = false;
+ }
+
+ while (retrieved < length)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled<int>(cancellationToken);
+ }
+
+ var returnedCount = await ReadAsync(buffer, offset + retrieved, length - retrieved, cancellationToken);
+ if (returnedCount <= 0)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.EndOfFile,
+ "Cannot read, Remote side has closed");
+ }
+ retrieved += returnedCount;
+ }
+ return retrieved;
+ }
+
+ public virtual async Task WriteAsync(byte[] buffer)
+ {
+ await WriteAsync(buffer, CancellationToken.None);
+ }
+
+ public virtual async Task WriteAsync(byte[] buffer, CancellationToken cancellationToken)
+ {
+ await WriteAsync(buffer, 0, buffer.Length, CancellationToken.None);
+ }
+
+ public virtual async Task WriteAsync(byte[] buffer, int offset, int length)
+ {
+ await WriteAsync(buffer, offset, length, CancellationToken.None);
+ }
+
+ public abstract Task WriteAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken);
+
+ public virtual async Task FlushAsync()
+ {
+ await FlushAsync(CancellationToken.None);
+ }
+
+ public abstract Task FlushAsync(CancellationToken cancellationToken);
+
+ protected abstract void Dispose(bool disposing);
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Transports/TServerTransport.cs b/lib/netcore/Thrift/Transports/TServerTransport.cs
new file mode 100644
index 000000000..d49feb6a0
--- /dev/null
+++ b/lib/netcore/Thrift/Transports/TServerTransport.cs
@@ -0,0 +1,54 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Thrift.Transports
+{
+ // ReSharper disable once InconsistentNaming
+ public abstract class TServerTransport
+ {
+ public abstract void Listen();
+ public abstract void Close();
+ public abstract bool IsClientPending();
+
+ protected virtual async Task<TClientTransport> AcceptImplementationAsync()
+ {
+ return await AcceptImplementationAsync(CancellationToken.None);
+ }
+
+ protected abstract Task<TClientTransport> AcceptImplementationAsync(CancellationToken cancellationToken);
+
+ public async Task<TClientTransport> AcceptAsync()
+ {
+ return await AcceptAsync(CancellationToken.None);
+ }
+
+ public async Task<TClientTransport> AcceptAsync(CancellationToken cancellationToken)
+ {
+ var transport = await AcceptImplementationAsync(cancellationToken);
+
+ if (transport == null)
+ {
+ throw new TTransportException("AcceptAsync() should not return null");
+ }
+
+ return transport;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Transports/TTransportException.cs b/lib/netcore/Thrift/Transports/TTransportException.cs
new file mode 100644
index 000000000..b7c42e33a
--- /dev/null
+++ b/lib/netcore/Thrift/Transports/TTransportException.cs
@@ -0,0 +1,58 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+namespace Thrift.Transports
+{
+ // ReSharper disable once InconsistentNaming
+ public class TTransportException : TException
+ {
+ public enum ExceptionType
+ {
+ Unknown,
+ NotOpen,
+ AlreadyOpen,
+ TimedOut,
+ EndOfFile,
+ Interrupted
+ }
+
+ protected ExceptionType ExType;
+
+ public TTransportException()
+ {
+ }
+
+ public TTransportException(ExceptionType exType)
+ : this()
+ {
+ ExType = exType;
+ }
+
+ public TTransportException(ExceptionType exType, string message)
+ : base(message)
+ {
+ ExType = exType;
+ }
+
+ public TTransportException(string message)
+ : base(message)
+ {
+ }
+
+ public ExceptionType Type => ExType;
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/Transports/TTransportFactory.cs b/lib/netcore/Thrift/Transports/TTransportFactory.cs
new file mode 100644
index 000000000..26c3cc471
--- /dev/null
+++ b/lib/netcore/Thrift/Transports/TTransportFactory.cs
@@ -0,0 +1,35 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+namespace Thrift.Transports
+{
+ /// <summary>
+ /// From Mark Slee & Aditya Agarwal of Facebook:
+ /// Factory class used to create wrapped instance of Transports.
+ /// This is used primarily in servers, which get Transports from
+ /// a ServerTransport and then may want to mutate them (i.e. create
+ /// a BufferedTransport from the underlying base transport)
+ /// </summary>
+ // ReSharper disable once InconsistentNaming
+ public class TTransportFactory
+ {
+ public virtual TClientTransport GetTransport(TClientTransport trans)
+ {
+ return trans;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/Thrift/project.json b/lib/netcore/Thrift/project.json
new file mode 100644
index 000000000..0eda41eac
--- /dev/null
+++ b/lib/netcore/Thrift/project.json
@@ -0,0 +1,19 @@
+{
+ "version": "1.0.0-*",
+ "dependencies": {
+ "Microsoft.AspNetCore.Http": "1.0.0",
+ "Microsoft.Extensions.Logging": "1.0.0",
+ "Microsoft.Extensions.Logging.Console": "1.0.0",
+ "Microsoft.Extensions.Logging.Debug": "1.0.0",
+ "NETStandard.Library": "1.6.0",
+ "System.IO.Pipes": "4.0.0",
+ "System.Net.NameResolution": "4.0.0",
+ "System.Net.Requests": "4.0.11",
+ "System.Net.Security": "4.0.0"
+ },
+ "frameworks": {
+ "netstandard1.6": {
+ "imports": "dnxcore50"
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/netcore/build.cmd b/lib/netcore/build.cmd
new file mode 100644
index 000000000..012b99db6
--- /dev/null
+++ b/lib/netcore/build.cmd
@@ -0,0 +1,36 @@
+@echo off
+rem /*
+rem * Licensed to the Apache Software Foundation (ASF) under one
+rem * or more contributor license agreements. See the NOTICE file
+rem * distributed with this work for additional information
+rem * regarding copyright ownership. The ASF licenses this file
+rem * to you under the Apache License, Version 2.0 (the
+rem * "License"); you may not use this file except in compliance
+rem * with the License. You may obtain a copy of the License at
+rem *
+rem * http://www.apache.org/licenses/LICENSE-2.0
+rem *
+rem * Unless required by applicable law or agreed to in writing,
+rem * software distributed under the License is distributed on an
+rem * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+rem * KIND, either express or implied. See the License for the
+rem * specific language governing permissions and limitations
+rem * under the License.
+rem */
+setlocal
+
+pushd Tests\Thrift.PublicInterfaces.Compile.Tests
+for %%a in (*.thrift) do thrift -gen netcore:wcf -r %%a
+thrift -gen netcore:wcf -r ..\..\..\..\contrib/fb303/if/fb303.thrift
+thrift -gen netcore:wcf -r ..\..\..\..\test/ThriftTest.thrift
+popd
+
+dotnet --info
+
+dotnet restore
+
+dotnet build **/*/project.json -r win10-x64
+dotnet build **/*/project.json -r osx.10.11-x64
+dotnet build **/*/project.json -r ubuntu.16.04-x64
+
+:eof
diff --git a/lib/netcore/build.sh b/lib/netcore/build.sh
new file mode 100755
index 000000000..1a58b749f
--- /dev/null
+++ b/lib/netcore/build.sh
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+#exit if any command fails
+#set -e
+
+pushd Tests/Thrift.PublicInterfaces.Compile.Tests
+for file in *.thrift
+do
+ ../../../../compiler/cpp/thrift -gen netcore:wcf -r "$file"
+done
+../../../../compiler/cpp/thrift -gen netcore:wcf -r ../../../../contrib/fb303/if/fb303.thrift
+../../../../compiler/cpp/thrift -gen netcore:wcf -r ../../../../test/ThriftTest.thrift
+popd
+
+dotnet --info
+
+dotnet restore
+
+# dotnet test ./test/TEST_PROJECT_NAME -c Release -f netcoreapp1.0
+
+# Instead, run directly with mono for the full .net version
+dotnet build **/*/project.json -r win10-x64
+dotnet build **/*/project.json -r osx.10.11-x64
+dotnet build **/*/project.json -r ubuntu.16.04-x64
+
+#revision=${TRAVIS_JOB_ID:=1}
+#revision=$(printf "%04d" $revision)
+
+#dotnet pack ./src/PROJECT_NAME -c Release -o ./artifacts --version-suffix=$revision
diff --git a/lib/netcore/global.json b/lib/netcore/global.json
new file mode 100644
index 000000000..8c0927574
--- /dev/null
+++ b/lib/netcore/global.json
@@ -0,0 +1,3 @@
+{
+ "projects": [ "." ]
+} \ No newline at end of file
diff --git a/test/Makefile.am b/test/Makefile.am
index ff780c396..51da3ba42 100755
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -72,6 +72,10 @@ if WITH_HAXE
SUBDIRS += haxe
endif
+if WITH_DOTNETCORE
+SUBDIRS += netcore
+endif
+
if WITH_GO
SUBDIRS += go
PRECROSS_TARGET += precross-go
diff --git a/test/ThriftTest.thrift b/test/ThriftTest.thrift
index ca7087375..c56f571d6 100644
--- a/test/ThriftTest.thrift
+++ b/test/ThriftTest.thrift
@@ -37,6 +37,7 @@ namespace delphi Thrift.Test
namespace cocoa ThriftTest
namespace lua ThriftTest
namespace xsd test (uri = 'http://thrift.apache.org/ns/ThriftTest')
+namespace netcore ThriftAsync.Test
// Presence of namespaces and sub-namespaces for which there is
// no generator should compile with warnings only
diff --git a/test/netcore/Makefile.am b/test/netcore/Makefile.am
new file mode 100644
index 000000000..21a6e7dde
--- /dev/null
+++ b/test/netcore/Makefile.am
@@ -0,0 +1,68 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+SUBDIRS = .
+
+THRIFT = $(top_builddir)/compiler/cpp/thrift
+
+GENDIR = ThriftTest/gen-netcore
+
+# Due to a known issue with "dotnet restore" the Thrift.dll dependency cannot be resolved from cmdline.
+# The problem does NOT affect Visual Studio builds, only cmdline.
+# - For details see https://github.com/dotnet/cli/issues/3199 and related tickets.
+# - Workaround is to temporarily copy the Thrift project into the solution
+COPYCMD = cp -u -p -r
+
+
+THRIFTCODE = \
+ ThriftTest/TestClient.cs \
+ ThriftTest/TestServer.cs \
+ ThriftTest/Properties/AssemblyInfo.cs \
+ ThriftTest/Program.cs
+
+all-local: \
+ ThriftTest.exe
+
+ThriftTest.exe: $(THRIFTCODE)
+ $(MKDIR_P) $(GENDIR)
+ $(THRIFT) -gen netcore:wcf -r -out $(GENDIR) $(top_srcdir)/test/ThriftTest.thrift
+ $(MKDIR_P) ./Thrift
+ $(COPYCMD) $(top_srcdir)/lib/netcore/Thrift/* ./Thrift
+ $(DOTNETCORE) --info
+ $(DOTNETCORE) restore
+ $(DOTNETCORE) build **/*/project.json -r win10-x64
+ $(DOTNETCORE) build **/*/project.json -r osx.10.11-x64
+ $(DOTNETCORE) build **/*/project.json -r ubuntu.16.04-x64
+
+clean-local:
+ $(RM) ThriftTest.exe
+ $(RM) -r $(GENDIR)
+ $(RM) -r ThriftTest/bin
+ $(RM) -r ThriftTest/obj
+ $(RM) -r Thrift
+
+EXTRA_DIST = \
+ $(THRIFTCODE) \
+ global.json \
+ ThriftTest/project.json \
+ ThriftTest/ThriftTest.sln \
+ ThriftTest/ThriftTest.xproj \
+ build.cmd \
+ build.sh
+
diff --git a/test/netcore/ThriftTest/Program.cs b/test/netcore/ThriftTest/Program.cs
new file mode 100644
index 000000000..94ed9d910
--- /dev/null
+++ b/test/netcore/ThriftTest/Program.cs
@@ -0,0 +1,76 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Collections.Generic;
+using Test;
+
+namespace ThriftTest
+{
+ public class Program
+ {
+ public static int Main(string[] args)
+ {
+ try
+ {
+ Console.SetBufferSize(Console.BufferWidth, 4096);
+ }
+ catch (Exception)
+ {
+ Console.WriteLine("Failed to grow scroll-back buffer");
+ }
+
+ // split mode and options
+ var subArgs = new List<string>(args);
+ var firstArg = string.Empty;
+ if (subArgs.Count > 0)
+ {
+ firstArg = subArgs[0];
+ subArgs.RemoveAt(0);
+ }
+
+ // run whatever mode is choosen
+ switch(firstArg)
+ {
+ case "client":
+ return TestClient.Execute(subArgs);
+ case "server":
+ return TestServer.Execute(subArgs);
+ case "--help":
+ PrintHelp();
+ return 0;
+ default:
+ PrintHelp();
+ return -1;
+ }
+ }
+
+ private static void PrintHelp()
+ {
+ Console.WriteLine("Usage:");
+ Console.WriteLine(" ThriftTest server [options]'");
+ Console.WriteLine(" ThriftTest client [options]'");
+ Console.WriteLine(" ThriftTest --help");
+ Console.WriteLine("");
+
+ TestServer.PrintOptionsHelp();
+ TestClient.PrintOptionsHelp();
+ }
+ }
+}
+
+
diff --git a/test/netcore/ThriftTest/Properties/AssemblyInfo.cs b/test/netcore/ThriftTest/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..efc9e3342
--- /dev/null
+++ b/test/netcore/ThriftTest/Properties/AssemblyInfo.cs
@@ -0,0 +1,43 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+
+[assembly: AssemblyTitle("ThriftTest")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("The Apache Software Foundation")]
+[assembly: AssemblyProduct("Thrift")]
+[assembly: AssemblyCopyright("The Apache Software Foundation")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+
+[assembly: Guid("B0C13DA0-3117-4844-8AE8-B1775E46223D")]
+
diff --git a/test/netcore/ThriftTest/Properties/launchSettings.json b/test/netcore/ThriftTest/Properties/launchSettings.json
new file mode 100644
index 000000000..ddafa79a4
--- /dev/null
+++ b/test/netcore/ThriftTest/Properties/launchSettings.json
@@ -0,0 +1,7 @@
+{
+ "profiles": {
+ "ThriftTest": {
+ "commandName": "Project"
+ }
+ }
+} \ No newline at end of file
diff --git a/test/netcore/ThriftTest/TestClient.cs b/test/netcore/ThriftTest/TestClient.cs
new file mode 100644
index 000000000..d9f95634c
--- /dev/null
+++ b/test/netcore/ThriftTest/TestClient.cs
@@ -0,0 +1,893 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Net;
+using System.Reflection;
+using System.Security.Authentication;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using ThriftAsync.Test;
+using Thrift.Collections;
+using Thrift.Protocols;
+using Thrift.Transports;
+using Thrift.Transports.Client;
+
+namespace Test
+{
+ public class TestClient
+ {
+ private class TestParams
+ {
+ public int numIterations = 1;
+ public IPAddress host = IPAddress.Loopback;
+ public int port = 9090;
+ public int numThreads = 1;
+ public string url;
+ public string pipe;
+ public bool buffered;
+ public bool framed;
+ public string protocol;
+ public bool encrypted = false;
+
+ internal void Parse( List<string> args)
+ {
+ for (var i = 0; i < args.Count; ++i)
+ {
+ if (args[i] == "-u")
+ {
+ url = args[++i];
+ }
+ else if (args[i] == "-n")
+ {
+ numIterations = Convert.ToInt32(args[++i]);
+ }
+ else if (args[i].StartsWith("--pipe="))
+ {
+ pipe = args[i].Substring(args[i].IndexOf("=") + 1);
+ Console.WriteLine("Using named pipes transport");
+ }
+ else if (args[i].StartsWith("--host="))
+ {
+ // check there for ipaddress
+ host = new IPAddress(Encoding.Unicode.GetBytes(args[i].Substring(args[i].IndexOf("=") + 1)));
+ }
+ else if (args[i].StartsWith("--port="))
+ {
+ port = int.Parse(args[i].Substring(args[i].IndexOf("=") + 1));
+ }
+ else if (args[i] == "-b" || args[i] == "--buffered" || args[i] == "--transport=buffered")
+ {
+ buffered = true;
+ Console.WriteLine("Using buffered sockets");
+ }
+ else if (args[i] == "-f" || args[i] == "--framed" || args[i] == "--transport=framed")
+ {
+ framed = true;
+ Console.WriteLine("Using framed transport");
+ }
+ else if (args[i] == "-t")
+ {
+ numThreads = Convert.ToInt32(args[++i]);
+ }
+ else if (args[i] == "--compact" || args[i] == "--protocol=compact")
+ {
+ protocol = "compact";
+ Console.WriteLine("Using compact protocol");
+ }
+ else if (args[i] == "--json" || args[i] == "--protocol=json")
+ {
+ protocol = "json";
+ Console.WriteLine("Using JSON protocol");
+ }
+ else if (args[i] == "--ssl")
+ {
+ encrypted = true;
+ Console.WriteLine("Using encrypted transport");
+ }
+ else
+ {
+ throw new ArgumentException(args[i]);
+ }
+ }
+ }
+
+ public TClientTransport CreateTransport()
+ {
+ if (url == null)
+ {
+ // endpoint transport
+ TClientTransport trans = null;
+
+ if (pipe != null)
+ {
+ trans = new TNamedPipeClientTransport(pipe);
+ }
+ else
+ {
+ if (encrypted)
+ {
+ var certPath = "../../keys/client.p12";
+ var cert = new X509Certificate2(certPath, "thrift");
+ trans = new TTlsSocketClientTransport(host, port, 0, cert, (o, c, chain, errors) => true, null, SslProtocols.Tls);
+ }
+ else
+ {
+ trans = new TSocketClientTransport(host, port);
+ }
+ }
+
+ // layered transport
+ if (buffered)
+ {
+ trans = new TBufferedClientTransport(trans);
+ }
+
+ if (framed)
+ {
+ trans = new TFramedClientTransport(trans);
+ }
+
+ return trans;
+ }
+
+ return new THttpClientTransport(new Uri(url), null);
+ }
+
+ public TProtocol CreateProtocol(TClientTransport transport)
+ {
+ if (protocol == "compact")
+ {
+ return new TCompactProtocol(transport);
+ }
+
+ if (protocol == "json")
+ {
+ return new TJsonProtocol(transport);
+ }
+
+ return new TBinaryProtocol(transport);
+ }
+ }
+
+
+ private const int ErrorBaseTypes = 1;
+ private const int ErrorStructs = 2;
+ private const int ErrorContainers = 4;
+ private const int ErrorExceptions = 8;
+ private const int ErrorUnknown = 64;
+
+ private class ClientTest
+ {
+ private readonly TClientTransport transport;
+ private readonly ThriftAsync.Test.ThriftTest.Client client;
+ private readonly int numIterations;
+ private bool done;
+
+ public int ReturnCode { get; set; }
+
+ public ClientTest(TestParams param)
+ {
+ transport = param.CreateTransport();
+ client = new ThriftAsync.Test.ThriftTest.Client(param.CreateProtocol(transport));
+ numIterations = param.numIterations;
+ }
+
+ public void Execute()
+ {
+ var token = CancellationToken.None;
+
+ if (done)
+ {
+ Console.WriteLine("Execute called more than once");
+ throw new InvalidOperationException();
+ }
+
+ for (var i = 0; i < numIterations; i++)
+ {
+ try
+ {
+ if (!transport.IsOpen)
+ {
+ transport.OpenAsync(token).GetAwaiter().GetResult();
+ }
+ }
+ catch (TTransportException ex)
+ {
+ Console.WriteLine("*** FAILED ***");
+ Console.WriteLine("Connect failed: " + ex.Message);
+ ReturnCode |= ErrorUnknown;
+ Console.WriteLine(ex.Message + " ST: " + ex.StackTrace);
+ continue;
+ }
+
+ try
+ {
+ ReturnCode |= ExecuteClientTestAsync(client).GetAwaiter().GetResult(); ;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("*** FAILED ***");
+ Console.WriteLine(ex.Message + " ST: " + ex.StackTrace);
+ ReturnCode |= ErrorUnknown;
+ }
+ }
+ try
+ {
+ transport.Close();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Error while closing transport");
+ Console.WriteLine(ex.Message + " ST: " + ex.StackTrace);
+ }
+ done = true;
+ }
+ }
+
+ internal static void PrintOptionsHelp()
+ {
+ Console.WriteLine("Client options:");
+ Console.WriteLine(" -u <URL>");
+ Console.WriteLine(" -t <# of threads to run> default = 1");
+ Console.WriteLine(" -n <# of iterations> per thread");
+ Console.WriteLine(" --pipe=<pipe name>");
+ Console.WriteLine(" --host=<IP address>");
+ Console.WriteLine(" --port=<port number>");
+ Console.WriteLine(" --transport=<transport name> one of buffered,framed (defaults to none)");
+ Console.WriteLine(" --protocol=<protocol name> one of compact,json (defaults to binary)");
+ Console.WriteLine(" --ssl");
+ Console.WriteLine();
+ }
+
+ public static int Execute(List<string> args)
+ {
+ try
+ {
+ var param = new TestParams();
+
+ try
+ {
+ param.Parse(args);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("*** FAILED ***");
+ Console.WriteLine("Error while parsing arguments");
+ Console.WriteLine(ex.Message + " ST: " + ex.StackTrace);
+ return ErrorUnknown;
+ }
+
+ var tests = Enumerable.Range(0, param.numThreads).Select(_ => new ClientTest(param)).ToArray();
+
+ //issue tests on separate threads simultaneously
+ var threads = tests.Select(test => new Thread(test.Execute)).ToArray();
+ var start = DateTime.Now;
+ foreach (var t in threads)
+ {
+ t.Start();
+ }
+
+ foreach (var t in threads)
+ {
+ t.Join();
+ }
+
+ Console.WriteLine("Total time: " + (DateTime.Now - start));
+ Console.WriteLine();
+ return tests.Select(t => t.ReturnCode).Aggregate((r1, r2) => r1 | r2);
+ }
+ catch (Exception outerEx)
+ {
+ Console.WriteLine("*** FAILED ***");
+ Console.WriteLine("Unexpected error");
+ Console.WriteLine(outerEx.Message + " ST: " + outerEx.StackTrace);
+ return ErrorUnknown;
+ }
+ }
+
+ public static string BytesToHex(byte[] data)
+ {
+ return BitConverter.ToString(data).Replace("-", string.Empty);
+ }
+
+ public static byte[] PrepareTestData(bool randomDist)
+ {
+ var retval = new byte[0x100];
+ var initLen = Math.Min(0x100, retval.Length);
+
+ // linear distribution, unless random is requested
+ if (!randomDist)
+ {
+ for (var i = 0; i < initLen; ++i)
+ {
+ retval[i] = (byte)i;
+ }
+ return retval;
+ }
+
+ // random distribution
+ for (var i = 0; i < initLen; ++i)
+ {
+ retval[i] = (byte)0;
+ }
+ var rnd = new Random();
+ for (var i = 1; i < initLen; ++i)
+ {
+ while (true)
+ {
+ var nextPos = rnd.Next() % initLen;
+ if (retval[nextPos] == 0)
+ {
+ retval[nextPos] = (byte)i;
+ break;
+ }
+ }
+ }
+ return retval;
+ }
+
+ public static async Task<int> ExecuteClientTestAsync(ThriftAsync.Test.ThriftTest.Client client)
+ {
+ var token = CancellationToken.None;
+ var returnCode = 0;
+
+ Console.Write("testVoid()");
+ await client.testVoidAsync(token);
+ Console.WriteLine(" = void");
+
+ Console.Write("testString(\"Test\")");
+ var s = await client.testStringAsync("Test", token);
+ Console.WriteLine(" = \"" + s + "\"");
+ if ("Test" != s)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorBaseTypes;
+ }
+
+ Console.Write("testBool(true)");
+ var t = await client.testBoolAsync((bool)true, token);
+ Console.WriteLine(" = " + t);
+ if (!t)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorBaseTypes;
+ }
+ Console.Write("testBool(false)");
+ var f = await client.testBoolAsync((bool)false, token);
+ Console.WriteLine(" = " + f);
+ if (f)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorBaseTypes;
+ }
+
+ Console.Write("testByte(1)");
+ var i8 = await client.testByteAsync((sbyte)1, token);
+ Console.WriteLine(" = " + i8);
+ if (1 != i8)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorBaseTypes;
+ }
+
+ Console.Write("testI32(-1)");
+ var i32 = await client.testI32Async(-1, token);
+ Console.WriteLine(" = " + i32);
+ if (-1 != i32)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorBaseTypes;
+ }
+
+ Console.Write("testI64(-34359738368)");
+ var i64 = await client.testI64Async(-34359738368, token);
+ Console.WriteLine(" = " + i64);
+ if (-34359738368 != i64)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorBaseTypes;
+ }
+
+ // TODO: Validate received message
+ Console.Write("testDouble(5.325098235)");
+ var dub = await client.testDoubleAsync(5.325098235, token);
+ Console.WriteLine(" = " + dub);
+ if (5.325098235 != dub)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorBaseTypes;
+ }
+ Console.Write("testDouble(-0.000341012439638598279)");
+ dub = await client.testDoubleAsync(-0.000341012439638598279, token);
+ Console.WriteLine(" = " + dub);
+ if (-0.000341012439638598279 != dub)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorBaseTypes;
+ }
+
+ var binOut = PrepareTestData(true);
+ Console.Write("testBinary(" + BytesToHex(binOut) + ")");
+ try
+ {
+ var binIn = await client.testBinaryAsync(binOut, token);
+ Console.WriteLine(" = " + BytesToHex(binIn));
+ if (binIn.Length != binOut.Length)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorBaseTypes;
+ }
+ for (var ofs = 0; ofs < Math.Min(binIn.Length, binOut.Length); ++ofs)
+ if (binIn[ofs] != binOut[ofs])
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorBaseTypes;
+ }
+ }
+ catch (Thrift.TApplicationException ex)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorBaseTypes;
+ Console.WriteLine(ex.Message + " ST: " + ex.StackTrace);
+ }
+
+ // binary equals? only with hashcode option enabled ...
+ Console.WriteLine("Test CrazyNesting");
+ var one = new CrazyNesting();
+ var two = new CrazyNesting();
+ one.String_field = "crazy";
+ two.String_field = "crazy";
+ one.Binary_field = new byte[] { 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0xFF };
+ two.Binary_field = new byte[10] { 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0xFF };
+ if (typeof(CrazyNesting).GetMethod("Equals")?.DeclaringType == typeof(CrazyNesting))
+ {
+ if (!one.Equals(two))
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorContainers;
+ throw new Exception("CrazyNesting.Equals failed");
+ }
+ }
+
+ // TODO: Validate received message
+ Console.Write("testStruct({\"Zero\", 1, -3, -5})");
+ var o = new Xtruct();
+ o.String_thing = "Zero";
+ o.Byte_thing = (sbyte)1;
+ o.I32_thing = -3;
+ o.I64_thing = -5;
+ var i = await client.testStructAsync(o, token);
+ Console.WriteLine(" = {\"" + i.String_thing + "\", " + i.Byte_thing + ", " + i.I32_thing + ", " + i.I64_thing + "}");
+
+ // TODO: Validate received message
+ Console.Write("testNest({1, {\"Zero\", 1, -3, -5}, 5})");
+ var o2 = new Xtruct2();
+ o2.Byte_thing = (sbyte)1;
+ o2.Struct_thing = o;
+ o2.I32_thing = 5;
+ var i2 = await client.testNestAsync(o2, token);
+ i = i2.Struct_thing;
+ Console.WriteLine(" = {" + i2.Byte_thing + ", {\"" + i.String_thing + "\", " + i.Byte_thing + ", " + i.I32_thing + ", " + i.I64_thing + "}, " + i2.I32_thing + "}");
+
+ var mapout = new Dictionary<int, int>();
+ for (var j = 0; j < 5; j++)
+ {
+ mapout[j] = j - 10;
+ }
+ Console.Write("testMap({");
+ var first = true;
+ foreach (var key in mapout.Keys)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ Console.Write(", ");
+ }
+ Console.Write(key + " => " + mapout[key]);
+ }
+ Console.Write("})");
+
+ var mapin = await client.testMapAsync(mapout, token);
+
+ Console.Write(" = {");
+ first = true;
+ foreach (var key in mapin.Keys)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ Console.Write(", ");
+ }
+ Console.Write(key + " => " + mapin[key]);
+ }
+ Console.WriteLine("}");
+
+ // TODO: Validate received message
+ var listout = new List<int>();
+ for (var j = -2; j < 3; j++)
+ {
+ listout.Add(j);
+ }
+ Console.Write("testList({");
+ first = true;
+ foreach (var j in listout)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ Console.Write(", ");
+ }
+ Console.Write(j);
+ }
+ Console.Write("})");
+
+ var listin = await client.testListAsync(listout, token);
+
+ Console.Write(" = {");
+ first = true;
+ foreach (var j in listin)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ Console.Write(", ");
+ }
+ Console.Write(j);
+ }
+ Console.WriteLine("}");
+
+ //set
+ // TODO: Validate received message
+ var setout = new THashSet<int>();
+ for (var j = -2; j < 3; j++)
+ {
+ setout.Add(j);
+ }
+ Console.Write("testSet({");
+ first = true;
+ foreach (int j in setout)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ Console.Write(", ");
+ }
+ Console.Write(j);
+ }
+ Console.Write("})");
+
+ var setin = await client.testSetAsync(setout, token);
+
+ Console.Write(" = {");
+ first = true;
+ foreach (int j in setin)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ Console.Write(", ");
+ }
+ Console.Write(j);
+ }
+ Console.WriteLine("}");
+
+
+ Console.Write("testEnum(ONE)");
+ var ret = await client.testEnumAsync(Numberz.ONE, token);
+ Console.WriteLine(" = " + ret);
+ if (Numberz.ONE != ret)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorStructs;
+ }
+
+ Console.Write("testEnum(TWO)");
+ ret = await client.testEnumAsync(Numberz.TWO, token);
+ Console.WriteLine(" = " + ret);
+ if (Numberz.TWO != ret)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorStructs;
+ }
+
+ Console.Write("testEnum(THREE)");
+ ret = await client.testEnumAsync(Numberz.THREE, token);
+ Console.WriteLine(" = " + ret);
+ if (Numberz.THREE != ret)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorStructs;
+ }
+
+ Console.Write("testEnum(FIVE)");
+ ret = await client.testEnumAsync(Numberz.FIVE, token);
+ Console.WriteLine(" = " + ret);
+ if (Numberz.FIVE != ret)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorStructs;
+ }
+
+ Console.Write("testEnum(EIGHT)");
+ ret = await client.testEnumAsync(Numberz.EIGHT, token);
+ Console.WriteLine(" = " + ret);
+ if (Numberz.EIGHT != ret)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorStructs;
+ }
+
+ Console.Write("testTypedef(309858235082523)");
+ var uid = await client.testTypedefAsync(309858235082523L, token);
+ Console.WriteLine(" = " + uid);
+ if (309858235082523L != uid)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorStructs;
+ }
+
+ // TODO: Validate received message
+ Console.Write("testMapMap(1)");
+ var mm = await client.testMapMapAsync(1, token);
+ Console.Write(" = {");
+ foreach (var key in mm.Keys)
+ {
+ Console.Write(key + " => {");
+ var m2 = mm[key];
+ foreach (var k2 in m2.Keys)
+ {
+ Console.Write(k2 + " => " + m2[k2] + ", ");
+ }
+ Console.Write("}, ");
+ }
+ Console.WriteLine("}");
+
+ // TODO: Validate received message
+ var insane = new Insanity();
+ insane.UserMap = new Dictionary<Numberz, long>();
+ insane.UserMap[Numberz.FIVE] = 5000L;
+ var truck = new Xtruct();
+ truck.String_thing = "Truck";
+ truck.Byte_thing = (sbyte)8;
+ truck.I32_thing = 8;
+ truck.I64_thing = 8;
+ insane.Xtructs = new List<Xtruct>();
+ insane.Xtructs.Add(truck);
+ Console.Write("testInsanity()");
+ var whoa = await client.testInsanityAsync(insane, token);
+ Console.Write(" = {");
+ foreach (var key in whoa.Keys)
+ {
+ var val = whoa[key];
+ Console.Write(key + " => {");
+
+ foreach (var k2 in val.Keys)
+ {
+ var v2 = val[k2];
+
+ Console.Write(k2 + " => {");
+ var userMap = v2.UserMap;
+
+ Console.Write("{");
+ if (userMap != null)
+ {
+ foreach (var k3 in userMap.Keys)
+ {
+ Console.Write(k3 + " => " + userMap[k3] + ", ");
+ }
+ }
+ else
+ {
+ Console.Write("null");
+ }
+ Console.Write("}, ");
+
+ var xtructs = v2.Xtructs;
+
+ Console.Write("{");
+ if (xtructs != null)
+ {
+ foreach (var x in xtructs)
+ {
+ Console.Write("{\"" + x.String_thing + "\", " + x.Byte_thing + ", " + x.I32_thing + ", " + x.I32_thing + "}, ");
+ }
+ }
+ else
+ {
+ Console.Write("null");
+ }
+ Console.Write("}");
+
+ Console.Write("}, ");
+ }
+ Console.Write("}, ");
+ }
+ Console.WriteLine("}");
+
+ sbyte arg0 = 1;
+ var arg1 = 2;
+ var arg2 = long.MaxValue;
+ var multiDict = new Dictionary<short, string>();
+ multiDict[1] = "one";
+
+ var tmpMultiDict = new List<string>();
+ foreach (var pair in multiDict)
+ tmpMultiDict.Add(pair.Key +" => "+ pair.Value);
+
+ var arg4 = Numberz.FIVE;
+ long arg5 = 5000000;
+ Console.Write("Test Multi(" + arg0 + "," + arg1 + "," + arg2 + ",{" + string.Join(",", tmpMultiDict) + "}," + arg4 + "," + arg5 + ")");
+ var multiResponse = await client.testMultiAsync(arg0, arg1, arg2, multiDict, arg4, arg5, token);
+ Console.Write(" = Xtruct(byte_thing:" + multiResponse.Byte_thing + ",String_thing:" + multiResponse.String_thing
+ + ",i32_thing:" + multiResponse.I32_thing + ",i64_thing:" + multiResponse.I64_thing + ")\n");
+
+ try
+ {
+ Console.WriteLine("testException(\"Xception\")");
+ await client.testExceptionAsync("Xception", token);
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorExceptions;
+ }
+ catch (Xception ex)
+ {
+ if (ex.ErrorCode != 1001 || ex.Message != "Xception")
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorExceptions;
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorExceptions;
+ Console.WriteLine(ex.Message + " ST: " + ex.StackTrace);
+ }
+ try
+ {
+ Console.WriteLine("testException(\"TException\")");
+ await client.testExceptionAsync("TException", token);
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorExceptions;
+ }
+ catch (Thrift.TException)
+ {
+ // OK
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorExceptions;
+ Console.WriteLine(ex.Message + " ST: " + ex.StackTrace);
+ }
+ try
+ {
+ Console.WriteLine("testException(\"ok\")");
+ await client.testExceptionAsync("ok", token);
+ // OK
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorExceptions;
+ Console.WriteLine(ex.Message + " ST: " + ex.StackTrace);
+ }
+
+ try
+ {
+ Console.WriteLine("testMultiException(\"Xception\", ...)");
+ await client.testMultiExceptionAsync("Xception", "ignore", token);
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorExceptions;
+ }
+ catch (Xception ex)
+ {
+ if (ex.ErrorCode != 1001 || ex.Message != "This is an Xception")
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorExceptions;
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorExceptions;
+ Console.WriteLine(ex.Message + " ST: " + ex.StackTrace);
+ }
+ try
+ {
+ Console.WriteLine("testMultiException(\"Xception2\", ...)");
+ await client.testMultiExceptionAsync("Xception2", "ignore", token);
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorExceptions;
+ }
+ catch (Xception2 ex)
+ {
+ if (ex.ErrorCode != 2002 || ex.Struct_thing.String_thing != "This is an Xception2")
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorExceptions;
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorExceptions;
+ Console.WriteLine(ex.Message + " ST: " + ex.StackTrace);
+ }
+ try
+ {
+ Console.WriteLine("testMultiException(\"success\", \"OK\")");
+ if ("OK" != (await client.testMultiExceptionAsync("success", "OK", token)).String_thing)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorExceptions;
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorExceptions;
+ Console.WriteLine(ex.Message + " ST: " + ex.StackTrace);
+ }
+
+ var sw = new Stopwatch();
+ sw.Start();
+ Console.WriteLine("Test Oneway(1)");
+ await client.testOnewayAsync(1, token);
+ sw.Stop();
+ if (sw.ElapsedMilliseconds > 1000)
+ {
+ Console.WriteLine("*** FAILED ***");
+ returnCode |= ErrorBaseTypes;
+ }
+
+ Console.Write("Test Calltime()");
+ var times = 50;
+ sw.Reset();
+ sw.Start();
+ for (var k = 0; k < times; ++k)
+ await client.testVoidAsync(token);
+ sw.Stop();
+ Console.WriteLine(" = {0} ms a testVoid() call", sw.ElapsedMilliseconds / times);
+ return returnCode;
+ }
+ }
+} \ No newline at end of file
diff --git a/test/netcore/ThriftTest/TestServer.cs b/test/netcore/ThriftTest/TestServer.cs
new file mode 100644
index 000000000..7976c5d7f
--- /dev/null
+++ b/test/netcore/ThriftTest/TestServer.cs
@@ -0,0 +1,556 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Collections.Generic;
+using System.Security.Authentication;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using ThriftAsync.Test;
+using Thrift;
+using Thrift.Collections;
+using Thrift.Protocols;
+using Thrift.Server;
+using Thrift.Transports;
+using Thrift.Transports.Server;
+
+namespace Test
+{
+ internal class ServerParam
+ {
+ internal bool useBufferedSockets = false;
+ internal bool useFramed = false;
+ internal bool useEncryption = false;
+ internal bool compact = false;
+ internal bool json = false;
+ internal int port = 9090;
+ internal string pipe = null;
+
+ internal void Parse(List<string> args)
+ {
+ for (var i = 0; i < args.Count; i++)
+ {
+ if (args[i].StartsWith("--pipe="))
+ {
+ pipe = args[i].Substring(args[i].IndexOf("=") + 1);
+ }
+ else if (args[i].StartsWith("--port="))
+ {
+ port = int.Parse(args[i].Substring(args[i].IndexOf("=") + 1));
+ }
+ else if (args[i] == "-b" || args[i] == "--buffered" || args[i] == "--transport=buffered")
+ {
+ useBufferedSockets = true;
+ }
+ else if (args[i] == "-f" || args[i] == "--framed" || args[i] == "--transport=framed")
+ {
+ useFramed = true;
+ }
+ else if (args[i] == "--compact" || args[i] == "--protocol=compact")
+ {
+ compact = true;
+ }
+ else if (args[i] == "--json" || args[i] == "--protocol=json")
+ {
+ json = true;
+ }
+ else if (args[i] == "--threaded" || args[i] == "--server-type=threaded")
+ {
+ throw new NotImplementedException(args[i]);
+ }
+ else if (args[i] == "--threadpool" || args[i] == "--server-type=threadpool")
+ {
+ throw new NotImplementedException(args[i]);
+ }
+ else if (args[i] == "--prototype" || args[i] == "--processor=prototype")
+ {
+ throw new NotImplementedException(args[i]);
+ }
+ else if (args[i] == "--ssl")
+ {
+ useEncryption = true;
+ }
+ else
+ {
+ throw new ArgumentException(args[i]);
+ }
+ }
+
+ }
+ }
+
+ public class TestServer
+ {
+ public static int _clientID = -1;
+ public delegate void TestLogDelegate(string msg, params object[] values);
+
+ public class MyServerEventHandler : TServerEventHandler
+ {
+ public int callCount = 0;
+
+ public Task PreServeAsync(CancellationToken cancellationToken)
+ {
+ callCount++;
+ return Task.CompletedTask;
+ }
+
+ public Task<object> CreateContextAsync(TProtocol input, TProtocol output, CancellationToken cancellationToken)
+ {
+ callCount++;
+ return Task.FromResult<object>(null);
+ }
+
+ public Task DeleteContextAsync(object serverContext, TProtocol input, TProtocol output, CancellationToken cancellationToken)
+ {
+ callCount++;
+ return Task.CompletedTask;
+ }
+
+ public Task ProcessContextAsync(object serverContext, TClientTransport transport, CancellationToken cancellationToken)
+ {
+ callCount++;
+ return Task.CompletedTask;
+ }
+ };
+
+ public class TestHandlerAsync : ThriftAsync.Test.ThriftTest.IAsync
+ {
+ public TBaseServer server { get; set; }
+ private int handlerID;
+ private StringBuilder sb = new StringBuilder();
+ private TestLogDelegate logger;
+
+ public TestHandlerAsync()
+ {
+ handlerID = Interlocked.Increment(ref _clientID);
+ logger += testConsoleLogger;
+ logger.Invoke("New TestHandler instance created");
+ }
+
+ public void testConsoleLogger(string msg, params object[] values)
+ {
+ sb.Clear();
+ sb.AppendFormat("handler{0:D3}:", handlerID);
+ sb.AppendFormat(msg, values);
+ sb.AppendLine();
+ Console.Write(sb.ToString());
+ }
+
+ public Task testVoidAsync(CancellationToken cancellationToken)
+ {
+ logger.Invoke("testVoid()");
+ return Task.CompletedTask;
+ }
+
+ public Task<string> testStringAsync(string thing, CancellationToken cancellationToken)
+ {
+ logger.Invoke("testString({0})", thing);
+ return Task.FromResult(thing);
+ }
+
+ public Task<bool> testBoolAsync(bool thing, CancellationToken cancellationToken)
+ {
+ logger.Invoke("testBool({0})", thing);
+ return Task.FromResult(thing);
+ }
+
+ public Task<sbyte> testByteAsync(sbyte thing, CancellationToken cancellationToken)
+ {
+ logger.Invoke("testByte({0})", thing);
+ return Task.FromResult(thing);
+ }
+
+ public Task<int> testI32Async(int thing, CancellationToken cancellationToken)
+ {
+ logger.Invoke("testI32({0})", thing);
+ return Task.FromResult(thing);
+ }
+
+ public Task<long> testI64Async(long thing, CancellationToken cancellationToken)
+ {
+ logger.Invoke("testI64({0})", thing);
+ return Task.FromResult(thing);
+ }
+
+ public Task<double> testDoubleAsync(double thing, CancellationToken cancellationToken)
+ {
+ logger.Invoke("testDouble({0})", thing);
+ return Task.FromResult(thing);
+ }
+
+ public Task<byte[]> testBinaryAsync(byte[] thing, CancellationToken cancellationToken)
+ {
+ var hex = BitConverter.ToString(thing).Replace("-", string.Empty);
+ logger.Invoke("testBinary({0:X})", hex);
+ return Task.FromResult(thing);
+ }
+
+ public Task<Xtruct> testStructAsync(Xtruct thing, CancellationToken cancellationToken)
+ {
+ logger.Invoke("testStruct({{\"{0}\", {1}, {2}, {3}}})", thing.String_thing, thing.Byte_thing, thing.I32_thing, thing.I64_thing);
+ return Task.FromResult(thing);
+ }
+
+ public Task<Xtruct2> testNestAsync(Xtruct2 nest, CancellationToken cancellationToken)
+ {
+ var thing = nest.Struct_thing;
+ logger.Invoke("testNest({{{0}, {{\"{1}\", {2}, {3}, {4}, {5}}}}})",
+ nest.Byte_thing,
+ thing.String_thing,
+ thing.Byte_thing,
+ thing.I32_thing,
+ thing.I64_thing,
+ nest.I32_thing);
+ return Task.FromResult(nest);
+ }
+
+ public Task<Dictionary<int, int>> testMapAsync(Dictionary<int, int> thing, CancellationToken cancellationToken)
+ {
+ sb.Clear();
+ sb.Append("testMap({{");
+ var first = true;
+ foreach (var key in thing.Keys)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ sb.Append(", ");
+ }
+ sb.AppendFormat("{0} => {1}", key, thing[key]);
+ }
+ sb.Append("}})");
+ logger.Invoke(sb.ToString());
+ return Task.FromResult(thing);
+ }
+
+ public Task<Dictionary<string, string>> testStringMapAsync(Dictionary<string, string> thing, CancellationToken cancellationToken)
+ {
+ sb.Clear();
+ sb.Append("testStringMap({{");
+ var first = true;
+ foreach (var key in thing.Keys)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ sb.Append(", ");
+ }
+ sb.AppendFormat("{0} => {1}", key, thing[key]);
+ }
+ sb.Append("}})");
+ logger.Invoke(sb.ToString());
+ return Task.FromResult(thing);
+ }
+
+ public Task<THashSet<int>> testSetAsync(THashSet<int> thing, CancellationToken cancellationToken)
+ {
+ sb.Clear();
+ sb.Append("testSet({{");
+ var first = true;
+ foreach (int elem in thing)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ sb.Append(", ");
+ }
+ sb.AppendFormat("{0}", elem);
+ }
+ sb.Append("}})");
+ logger.Invoke(sb.ToString());
+ return Task.FromResult(thing);
+ }
+
+ public Task<List<int>> testListAsync(List<int> thing, CancellationToken cancellationToken)
+ {
+ sb.Clear();
+ sb.Append("testList({{");
+ var first = true;
+ foreach (var elem in thing)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ sb.Append(", ");
+ }
+ sb.AppendFormat("{0}", elem);
+ }
+ sb.Append("}})");
+ logger.Invoke(sb.ToString());
+ return Task.FromResult(thing);
+ }
+
+ public Task<Numberz> testEnumAsync(Numberz thing, CancellationToken cancellationToken)
+ {
+ logger.Invoke("testEnum({0})", thing);
+ return Task.FromResult(thing);
+ }
+
+ public Task<long> testTypedefAsync(long thing, CancellationToken cancellationToken)
+ {
+ logger.Invoke("testTypedef({0})", thing);
+ return Task.FromResult(thing);
+ }
+
+ public Task<Dictionary<int, Dictionary<int, int>>> testMapMapAsync(int hello, CancellationToken cancellationToken)
+ {
+ logger.Invoke("testMapMap({0})", hello);
+ var mapmap = new Dictionary<int, Dictionary<int, int>>();
+
+ var pos = new Dictionary<int, int>();
+ var neg = new Dictionary<int, int>();
+ for (var i = 1; i < 5; i++)
+ {
+ pos[i] = i;
+ neg[-i] = -i;
+ }
+
+ mapmap[4] = pos;
+ mapmap[-4] = neg;
+
+ return Task.FromResult(mapmap);
+ }
+
+ public Task<Dictionary<long, Dictionary<Numberz, Insanity>>> testInsanityAsync(Insanity argument, CancellationToken cancellationToken)
+ {
+ logger.Invoke("testInsanity()");
+
+ /** from ThriftTest.thrift:
+ * So you think you've got this all worked, out eh?
+ *
+ * Creates a the returned map with these values and prints it out:
+ * { 1 => { 2 => argument,
+ * 3 => argument,
+ * },
+ * 2 => { 6 => <empty Insanity struct>, },
+ * }
+ * @return map<UserId, map<Numberz,Insanity>> - a map with the above values
+ */
+
+ var first_map = new Dictionary<Numberz, Insanity>();
+ var second_map = new Dictionary<Numberz, Insanity>(); ;
+
+ first_map[Numberz.TWO] = argument;
+ first_map[Numberz.THREE] = argument;
+
+ second_map[Numberz.SIX] = new Insanity();
+
+ var insane = new Dictionary<long, Dictionary<Numberz, Insanity>>
+ {
+ [1] = first_map,
+ [2] = second_map
+ };
+
+ return Task.FromResult(insane);
+ }
+
+ public Task<Xtruct> testMultiAsync(sbyte arg0, int arg1, long arg2, Dictionary<short, string> arg3, Numberz arg4, long arg5,
+ CancellationToken cancellationToken)
+ {
+ logger.Invoke("testMulti()");
+
+ var hello = new Xtruct(); ;
+ hello.String_thing = "Hello2";
+ hello.Byte_thing = arg0;
+ hello.I32_thing = arg1;
+ hello.I64_thing = arg2;
+ return Task.FromResult(hello);
+ }
+
+ public Task testExceptionAsync(string arg, CancellationToken cancellationToken)
+ {
+ logger.Invoke("testException({0})", arg);
+ if (arg == "Xception")
+ {
+ var x = new Xception
+ {
+ ErrorCode = 1001,
+ Message = arg
+ };
+ throw x;
+ }
+ if (arg == "TException")
+ {
+ throw new TException();
+ }
+ return Task.CompletedTask;
+ }
+
+ public Task<Xtruct> testMultiExceptionAsync(string arg0, string arg1, CancellationToken cancellationToken)
+ {
+ logger.Invoke("testMultiException({0}, {1})", arg0, arg1);
+ if (arg0 == "Xception")
+ {
+ var x = new Xception
+ {
+ ErrorCode = 1001,
+ Message = "This is an Xception"
+ };
+ throw x;
+ }
+
+ if (arg0 == "Xception2")
+ {
+ var x = new Xception2
+ {
+ ErrorCode = 2002,
+ Struct_thing = new Xtruct { String_thing = "This is an Xception2" }
+ };
+ throw x;
+ }
+
+ var result = new Xtruct { String_thing = arg1 };
+ return Task.FromResult(result);
+ }
+
+ public Task testOnewayAsync(int secondsToSleep, CancellationToken cancellationToken)
+ {
+ logger.Invoke("testOneway({0}), sleeping...", secondsToSleep);
+ Thread.Sleep(secondsToSleep * 1000);
+ logger.Invoke("testOneway finished");
+
+ return Task.CompletedTask;
+ }
+ }
+
+
+ private enum ProcessorFactoryType
+ {
+ TSingletonProcessorFactory,
+ TPrototypeProcessorFactory,
+ }
+
+ internal static void PrintOptionsHelp()
+ {
+ Console.WriteLine("Server options:");
+ Console.WriteLine(" --pipe=<pipe name>");
+ Console.WriteLine(" --port=<port number>");
+ Console.WriteLine(" --transport=<transport name> one of buffered,framed (defaults to none)");
+ Console.WriteLine(" --protocol=<protocol name> one of compact,json (defaults to binary)");
+ Console.WriteLine(" --server-type=<type> one of threaded,threadpool (defaults to simple)");
+ Console.WriteLine(" --processor=<prototype>");
+ Console.WriteLine(" --ssl");
+ Console.WriteLine();
+ }
+
+ public static int Execute(List<string> args)
+ {
+ var logger = new LoggerFactory().CreateLogger("Test");
+
+ try
+ {
+ var param = new ServerParam();
+
+ try
+ {
+ param.Parse(args);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("*** FAILED ***");
+ Console.WriteLine("Error while parsing arguments");
+ Console.WriteLine(ex.Message + " ST: " + ex.StackTrace);
+ return 1;
+ }
+
+
+ // Transport
+ TServerTransport trans;
+ if (param.pipe != null)
+ {
+ trans = new TNamedPipeServerTransport(param.pipe);
+ }
+ else
+ {
+ if (param.useEncryption)
+ {
+ var certPath = "../keys/server.p12";
+ trans = new TTlsServerSocketTransport(param.port, param.useBufferedSockets, new X509Certificate2(certPath, "thrift"), null, null, SslProtocols.Tls12);
+ }
+ else
+ {
+ trans = new TServerSocketTransport(param.port, 0, param.useBufferedSockets);
+ }
+ }
+
+ ITProtocolFactory proto;
+ if (param.compact)
+ proto = new TCompactProtocol.Factory();
+ else if (param.json)
+ proto = new TJsonProtocol.Factory();
+ else
+ proto = new TBinaryProtocol.Factory();
+
+ ITProcessorFactory processorFactory;
+
+ // Processor
+ var testHandler = new TestHandlerAsync();
+ var testProcessor = new ThriftAsync.Test.ThriftTest.AsyncProcessor(testHandler);
+ processorFactory = new SingletonTProcessorFactory(testProcessor);
+
+
+ TTransportFactory transFactory;
+ if (param.useFramed)
+ throw new NotImplementedException("framed"); // transFactory = new TFramedTransport.Factory();
+ else
+ transFactory = new TTransportFactory();
+
+ TBaseServer serverEngine = new AsyncBaseServer(processorFactory, trans, transFactory, transFactory, proto, proto, logger);
+
+ //Server event handler
+ var serverEvents = new MyServerEventHandler();
+ serverEngine.SetEventHandler(serverEvents);
+
+ // Run it
+ var where = (! string.IsNullOrEmpty(param.pipe)) ? "on pipe " + param.pipe : "on port " + param.port;
+ Console.WriteLine("Starting the AsyncBaseServer " + where +
+ " with processor TPrototypeProcessorFactory prototype factory " +
+ (param.useBufferedSockets ? " with buffered socket" : "") +
+ (param.useFramed ? " with framed transport" : "") +
+ (param.useEncryption ? " with encryption" : "") +
+ (param.compact ? " with compact protocol" : "") +
+ (param.json ? " with json protocol" : "") +
+ "...");
+ serverEngine.ServeAsync(CancellationToken.None).GetAwaiter().GetResult();
+ Console.ReadLine();
+ }
+ catch (Exception x)
+ {
+ Console.Error.Write(x);
+ return 1;
+ }
+ Console.WriteLine("done.");
+ return 0;
+ }
+ }
+
+}
diff --git a/test/netcore/ThriftTest/ThriftTest.sln b/test/netcore/ThriftTest/ThriftTest.sln
new file mode 100644
index 000000000..03b4f3d67
--- /dev/null
+++ b/test/netcore/ThriftTest/ThriftTest.sln
@@ -0,0 +1,33 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ThriftTest", "ThriftTest.xproj", "{B0C13DA0-3117-4844-8AE8-B1775E46223D}"
+EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Thrift", "..\..\..\lib\netcore\Thrift\Thrift.xproj", "{6850CF46-5467-4C65-BD78-871581C539FC}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{839DBA0F-2D58-4266-A30D-3392BD710A59}"
+ ProjectSection(SolutionItems) = preProject
+ ..\global.json = ..\global.json
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B0C13DA0-3117-4844-8AE8-B1775E46223D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B0C13DA0-3117-4844-8AE8-B1775E46223D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B0C13DA0-3117-4844-8AE8-B1775E46223D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B0C13DA0-3117-4844-8AE8-B1775E46223D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6850CF46-5467-4C65-BD78-871581C539FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6850CF46-5467-4C65-BD78-871581C539FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6850CF46-5467-4C65-BD78-871581C539FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6850CF46-5467-4C65-BD78-871581C539FC}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/test/netcore/ThriftTest/ThriftTest.xproj b/test/netcore/ThriftTest/ThriftTest.xproj
new file mode 100644
index 000000000..7746cc88e
--- /dev/null
+++ b/test/netcore/ThriftTest/ThriftTest.xproj
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
+ <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
+ </PropertyGroup>
+
+ <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>B0C13DA0-3117-4844-8AE8-B1775E46223D</ProjectGuid>
+ <RootNamespace>ThriftTest</RootNamespace>
+ <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
+ <OutputPath Condition="'$(OutputPath)'=='' ">bin\$(MSBuildProjectName)\</OutputPath>
+ <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <SchemaVersion>2.0</SchemaVersion>
+ </PropertyGroup>
+ <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
+</Project>
diff --git a/test/netcore/ThriftTest/project.json b/test/netcore/ThriftTest/project.json
new file mode 100644
index 000000000..56d277773
--- /dev/null
+++ b/test/netcore/ThriftTest/project.json
@@ -0,0 +1,29 @@
+{
+ "version": "1.0.0-*",
+ "buildOptions": {
+ "emitEntryPoint": true
+ },
+
+ "runtimes": {
+ "win10-x64": {},
+ "osx.10.11-x64": {},
+ "ubuntu.16.04-x64": {}
+ },
+
+ "dependencies": {
+ "System.Runtime.Serialization.Primitives": "4.1.1",
+ "System.ServiceModel.Primitives": "4.0.0"
+ },
+
+ "frameworks": {
+ "netcoreapp1.0": {
+ "imports": "dnxcore50",
+ "dependencies": {
+ "Thrift": "1.0.0-*",
+ "Microsoft.NETCore.App": {
+ "version": "1.0.0"
+ }
+ }
+ }
+ }
+}
diff --git a/test/netcore/build.cmd b/test/netcore/build.cmd
new file mode 100644
index 000000000..88ff20aee
--- /dev/null
+++ b/test/netcore/build.cmd
@@ -0,0 +1,45 @@
+@echo off
+rem /*
+rem * Licensed to the Apache Software Foundation (ASF) under one
+rem * or more contributor license agreements. See the NOTICE file
+rem * distributed with this work for additional information
+rem * regarding copyright ownership. The ASF licenses this file
+rem * to you under the Apache License, Version 2.0 (the
+rem * "License"); you may not use this file except in compliance
+rem * with the License. You may obtain a copy of the License at
+rem *
+rem * http://www.apache.org/licenses/LICENSE-2.0
+rem *
+rem * Unless required by applicable law or agreed to in writing,
+rem * software distributed under the License is distributed on an
+rem * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+rem * KIND, either express or implied. See the License for the
+rem * specific language governing permissions and limitations
+rem * under the License.
+rem */
+setlocal
+
+cd ThriftTest
+thrift -gen netcore:wcf -r ..\..\ThriftTest.thrift
+cd ..
+
+rem * Due to a known issue with "dotnet restore" the Thrift.dll dependency cannot be resolved from cmdline
+rem * For details see https://github.com/dotnet/cli/issues/3199 and related tickets
+rem * The problem does NOT affect Visual Studio builds.
+
+rem * workaround for "dotnet restore" issue
+xcopy ..\..\lib\netcore\Thrift .\Thrift /YSEI >NUL
+
+dotnet --info
+dotnet restore
+
+dotnet build **/*/project.json -r win10-x64
+dotnet build **/*/project.json -r osx.10.11-x64
+dotnet build **/*/project.json -r ubuntu.16.04-x64
+
+rem * workaround for "dotnet restore" issue
+del .\Thrift\* /Q /S >NUL
+rd .\Thrift /Q /S >NUL
+
+
+:eof
diff --git a/test/netcore/build.sh b/test/netcore/build.sh
new file mode 100644
index 000000000..3acd78a20
--- /dev/null
+++ b/test/netcore/build.sh
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+#exit if any command fails
+#set -e
+
+cd ThriftTest
+../../../compiler/cpp/thrift -gen netcore:wcf -r ../../ThriftTest.thrift
+cd ..
+
+
+# Due to a known issue with "dotnet restore" the Thrift.dll dependency cannot be resolved from cmdline
+# For details see https://github.com/dotnet/cli/issues/3199 and related tickets
+# The problem does NOT affect Visual Studio builds.
+
+# workaround for "dotnet restore" issue
+cp -u -p -r ..\..\lib\netcore\Thrift .\Thrift
+
+dotnet --info
+dotnet restore
+
+# dotnet test ./test/TEST_PROJECT_NAME -c Release -f netcoreapp1.0
+
+# Instead, run directly with mono for the full .net version
+dotnet build **/*/project.json -r win10-x64
+dotnet build **/*/project.json -r osx.10.11-x64
+dotnet build **/*/project.json -r ubuntu.16.04-x64
+
+#revision=${TRAVIS_JOB_ID:=1}
+#revision=$(printf "%04d" $revision)
+
+#dotnet pack ./src/PROJECT_NAME -c Release -o ./artifacts --version-suffix=$revision
+
+# workaround for "dotnet restore" issue
+rm -r .\Thrift
+
diff --git a/test/netcore/global.json b/test/netcore/global.json
new file mode 100644
index 000000000..53f181109
--- /dev/null
+++ b/test/netcore/global.json
@@ -0,0 +1,3 @@
+{
+ "projects": [ "../../lib/netcore" ]
+} \ No newline at end of file
diff --git a/test/tests.json b/test/tests.json
index 2460b837e..b101bfda5 100644
--- a/test/tests.json
+++ b/test/tests.json
@@ -364,6 +364,34 @@
"workdir": "csharp"
},
{
+ "name": "netcore",
+ "transports": [
+ "buffered",
+ "framed"
+ ],
+ "sockets": [
+ "ip",
+ "ip-ssl"
+ ],
+ "protocols": [
+ "binary",
+ "compact",
+ "json"
+ ],
+ "server": {
+ "command": [
+ "dotnet restore && dotnet run server"
+ ]
+ },
+ "client": {
+ "timeout": 10,
+ "command": [
+ "dotnet run client"
+ ]
+ },
+ "workdir": "netcore/ThriftTest"
+ },
+ {
"name": "perl",
"transports": [
"buffered",
diff --git a/tutorial/Makefile.am b/tutorial/Makefile.am
index 5865c54aa..efa314ae5 100755
--- a/tutorial/Makefile.am
+++ b/tutorial/Makefile.am
@@ -58,6 +58,10 @@ if WITH_HAXE
SUBDIRS += haxe
endif
+if WITH_DOTNETCORE
+SUBDIRS += netcore
+endif
+
if WITH_GO
SUBDIRS += go
endif
diff --git a/tutorial/netcore/.gitignore b/tutorial/netcore/.gitignore
new file mode 100644
index 000000000..9938bb237
--- /dev/null
+++ b/tutorial/netcore/.gitignore
@@ -0,0 +1 @@
+!**/*.pfx \ No newline at end of file
diff --git a/tutorial/netcore/Client/Client.xproj b/tutorial/netcore/Client/Client.xproj
new file mode 100644
index 000000000..872618212
--- /dev/null
+++ b/tutorial/netcore/Client/Client.xproj
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
+ <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
+ </PropertyGroup>
+
+ <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>de78a01b-f7c6-49d1-97da-669d2ed37641</ProjectGuid>
+ <RootNamespace>Client</RootNamespace>
+ <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
+ <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
+ <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <SchemaVersion>2.0</SchemaVersion>
+ </PropertyGroup>
+ <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
+</Project>
diff --git a/tutorial/netcore/Client/Program.cs b/tutorial/netcore/Client/Program.cs
new file mode 100644
index 000000000..5485e9503
--- /dev/null
+++ b/tutorial/netcore/Client/Program.cs
@@ -0,0 +1,277 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Thrift;
+using Thrift.Protocols;
+using Thrift.Transports;
+using Thrift.Transports.Client;
+using tutorial;
+using shared;
+
+namespace Client
+{
+ public class Program
+ {
+ private static readonly ILogger Logger = new LoggerFactory().CreateLogger(nameof(Client));
+
+ private static void DisplayHelp()
+ {
+ Console.WriteLine(@"
+Usage:
+ Client.exe -h
+ will diplay help information
+
+ Client.exe -t:<transport> -p:<protocol>
+ will run client with specified arguments (tcp transport and binary protocol by default)
+
+Options:
+ -t (transport):
+ tcp - (default) tcp transport will be used (host - ""localhost"", port - 9090)
+ tcpbuffered - buffered transport over tcp will be used (host - ""localhost"", port - 9090)
+ namedpipe - namedpipe transport will be used (pipe address - "".test"")
+ http - http transport will be used (address - ""http://localhost:9090"")
+ tcptls - tcp tls transport will be used (host - ""localhost"", port - 9090)
+
+ -p (protocol):
+ binary - (default) binary protocol will be used
+ compact - compact protocol will be used
+ json - json protocol will be used
+
+Sample:
+ Client.exe -t:tcp -p:binary
+");
+ }
+
+ public static void Main(string[] args)
+ {
+ args = args ?? new string[0];
+
+ if (args.Any(x => x.StartsWith("-h", StringComparison.OrdinalIgnoreCase)))
+ {
+ DisplayHelp();
+ return;
+ }
+
+
+ using (var source = new CancellationTokenSource())
+ {
+ RunAsync(args, source.Token).GetAwaiter().GetResult();
+ }
+ }
+
+ private static async Task RunAsync(string[] args, CancellationToken cancellationToken)
+ {
+ var clientTransport = GetTransport(args);
+
+ Logger.LogInformation($"Selected client transport: {clientTransport}");
+
+ var clientProtocol = GetProtocol(args, clientTransport);
+
+ Logger.LogInformation($"Selected client protocol: {clientProtocol}");
+
+ await RunClientAsync(clientProtocol, cancellationToken);
+ }
+
+ private static TClientTransport GetTransport(string[] args)
+ {
+ var transport = args.FirstOrDefault(x => x.StartsWith("-t"))?.Split(':')?[1];
+
+ Transport selectedTransport;
+ if (Enum.TryParse(transport, true, out selectedTransport))
+ {
+ switch (selectedTransport)
+ {
+ case Transport.Tcp:
+ return new TSocketClientTransport(IPAddress.Loopback, 9090);
+ case Transport.NamedPipe:
+ return new TNamedPipeClientTransport(".test");
+ case Transport.Http:
+ return new THttpClientTransport(new Uri("http://localhost:9090"), null);
+ case Transport.TcpBuffered:
+ return
+ new TBufferedClientTransport(
+ new TSocketClientTransport(IPAddress.Loopback, 9090));
+ case Transport.TcpTls:
+ return new TTlsSocketClientTransport(IPAddress.Loopback, 9090,
+ GetCertificate(), CertValidator, LocalCertificateSelectionCallback);
+ case Transport.Framed:
+ throw new NotSupportedException("Framed is not ready for samples");
+ }
+ }
+
+ return new TSocketClientTransport(IPAddress.Loopback, 9090);
+ }
+
+ private static X509Certificate2 GetCertificate()
+ {
+ // due to files location in net core better to take certs from top folder
+ var certFile = GetCertPath(Directory.GetParent(Directory.GetCurrentDirectory()));
+ return new X509Certificate2(certFile, "ThriftTest");
+ }
+
+ private static string GetCertPath(DirectoryInfo di, int maxCount = 6)
+ {
+ var topDir = di;
+ var certFile =
+ topDir.EnumerateFiles("ThriftTest.pfx", SearchOption.AllDirectories)
+ .FirstOrDefault();
+ if (certFile == null)
+ {
+ if (maxCount == 0)
+ throw new FileNotFoundException("Cannot find file in directories");
+ return GetCertPath(di.Parent, maxCount - 1);
+ }
+
+ return certFile.FullName;
+ }
+
+ private static X509Certificate LocalCertificateSelectionCallback(object sender,
+ string targetHost, X509CertificateCollection localCertificates,
+ X509Certificate remoteCertificate, string[] acceptableIssuers)
+ {
+ return GetCertificate();
+ }
+
+ private static bool CertValidator(object sender, X509Certificate certificate,
+ X509Chain chain, SslPolicyErrors sslPolicyErrors)
+ {
+ return true;
+ }
+
+ private static TProtocol GetProtocol(string[] args, TClientTransport transport)
+ {
+ var protocol = args.FirstOrDefault(x => x.StartsWith("-p"))?.Split(':')?[1];
+
+ Protocol selectedProtocol;
+ if (Enum.TryParse(protocol, true, out selectedProtocol))
+ {
+ switch (selectedProtocol)
+ {
+ case Protocol.Binary:
+ return new TBinaryProtocol(transport);
+ case Protocol.Compact:
+ return new TCompactProtocol(transport);
+ case Protocol.Json:
+ return new TJsonProtocol(transport);
+ }
+ }
+
+ return new TBinaryProtocol(transport);
+ }
+
+ private static async Task RunClientAsync(TProtocol protocol,
+ CancellationToken cancellationToken)
+ {
+ try
+ {
+ var client = new Calculator.Client(protocol);
+ await client.OpenTransportAsync(cancellationToken);
+
+ try
+ {
+ // Async version
+
+ Logger.LogInformation("PingAsync()");
+ await client.pingAsync(cancellationToken);
+
+ Logger.LogInformation("AddAsync(1,1)");
+ var sum = await client.addAsync(1, 1, cancellationToken);
+ Logger.LogInformation($"AddAsync(1,1)={sum}");
+
+ var work = new Work
+ {
+ Op = Operation.DIVIDE,
+ Num1 = 1,
+ Num2 = 0
+ };
+
+ try
+ {
+ Logger.LogInformation("CalculateAsync(1)");
+ await client.calculateAsync(1, work, cancellationToken);
+ Logger.LogInformation("Whoa we can divide by 0");
+ }
+ catch (InvalidOperation io)
+ {
+ Logger.LogInformation("Invalid operation: " + io);
+ }
+
+ work.Op = Operation.SUBTRACT;
+ work.Num1 = 15;
+ work.Num2 = 10;
+
+ try
+ {
+ Logger.LogInformation("CalculateAsync(1)");
+ var diff = await client.calculateAsync(1, work, cancellationToken);
+ Logger.LogInformation($"15-10={diff}");
+ }
+ catch (InvalidOperation io)
+ {
+ Logger.LogInformation("Invalid operation: " + io);
+ }
+
+ Logger.LogInformation("GetStructAsync(1)");
+ var log = await client.getStructAsync(1, cancellationToken);
+ Logger.LogInformation($"Check log: {log.Value}");
+
+ Logger.LogInformation("ZipAsync() with delay 100mc on server side");
+ await client.zipAsync(cancellationToken);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex.ToString());
+ }
+ finally
+ {
+ protocol.Transport.Close();
+ }
+ }
+ catch (TApplicationException x)
+ {
+ Logger.LogError(x.ToString());
+ }
+ }
+
+ private enum Transport
+ {
+ Tcp,
+ NamedPipe,
+ Http,
+ TcpBuffered,
+ Framed,
+ TcpTls
+ }
+
+ private enum Protocol
+ {
+ Binary,
+ Compact,
+ Json,
+ }
+ }
+} \ No newline at end of file
diff --git a/tutorial/netcore/Client/Properties/AssemblyInfo.cs b/tutorial/netcore/Client/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..568382e66
--- /dev/null
+++ b/tutorial/netcore/Client/Properties/AssemblyInfo.cs
@@ -0,0 +1,40 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("The Apache Software Foundation")]
+[assembly: AssemblyProduct("Thrift")]
+[assembly: AssemblyCopyright("The Apache Software Foundation")]
+[assembly: AssemblyTrademark("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+
+[assembly: Guid("de78a01b-f7c6-49d1-97da-669d2ed37641")] \ No newline at end of file
diff --git a/tutorial/netcore/Client/Properties/launchSettings.json b/tutorial/netcore/Client/Properties/launchSettings.json
new file mode 100644
index 000000000..f351eeb09
--- /dev/null
+++ b/tutorial/netcore/Client/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "Client": {
+ "commandName": "Project",
+ "commandLineArgs": "-t:tcptls"
+ }
+ }
+} \ No newline at end of file
diff --git a/tutorial/netcore/Client/ThriftTest.pfx b/tutorial/netcore/Client/ThriftTest.pfx
new file mode 100644
index 000000000..f0ded2817
--- /dev/null
+++ b/tutorial/netcore/Client/ThriftTest.pfx
Binary files differ
diff --git a/tutorial/netcore/Client/project.json b/tutorial/netcore/Client/project.json
new file mode 100644
index 000000000..c850e5d55
--- /dev/null
+++ b/tutorial/netcore/Client/project.json
@@ -0,0 +1,28 @@
+{
+ "version": "1.0.0-*",
+ "buildOptions": {
+ "debugType": "portable",
+ "emitEntryPoint": true
+ },
+
+ "dependencies": {
+ "Interfaces": "1.0.0-*",
+ "Microsoft.NETCore.App": {
+ "version": "1.0.0"
+ },
+ "Thrift": "1.0.0-*",
+ //"Thrift": "1.0.0-*"
+ },
+
+ "runtimes": {
+ "win10-x64": {},
+ "osx.10.11-x64": {},
+ "ubuntu.16.04-x64": {}
+ },
+
+ "frameworks": {
+ "netcoreapp1.0": {
+ "imports": "dnxcore50"
+ }
+ }
+}
diff --git a/tutorial/netcore/Interfaces/Interfaces.xproj b/tutorial/netcore/Interfaces/Interfaces.xproj
new file mode 100644
index 000000000..d472ce6d3
--- /dev/null
+++ b/tutorial/netcore/Interfaces/Interfaces.xproj
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
+ <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
+ </PropertyGroup>
+
+ <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>4d13163d-9067-4c9c-8af0-64e08451397d</ProjectGuid>
+ <RootNamespace>Interfaces</RootNamespace>
+ <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
+ <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
+ <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <SchemaVersion>2.0</SchemaVersion>
+ </PropertyGroup>
+ <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
+</Project>
diff --git a/tutorial/netcore/Interfaces/Properties/AssemblyInfo.cs b/tutorial/netcore/Interfaces/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..9126b173e
--- /dev/null
+++ b/tutorial/netcore/Interfaces/Properties/AssemblyInfo.cs
@@ -0,0 +1,40 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("The Apache Software Foundation")]
+[assembly: AssemblyProduct("Thrift")]
+[assembly: AssemblyCopyright("The Apache Software Foundation")]
+[assembly: AssemblyTrademark("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+
+[assembly: Guid("4d13163d-9067-4c9c-8af0-64e08451397d")] \ No newline at end of file
diff --git a/tutorial/netcore/Interfaces/project.json b/tutorial/netcore/Interfaces/project.json
new file mode 100644
index 000000000..b5f7c989a
--- /dev/null
+++ b/tutorial/netcore/Interfaces/project.json
@@ -0,0 +1,22 @@
+{
+ "version": "1.0.0-*",
+
+ "dependencies": {
+ "NETStandard.Library": "1.6.0",
+ "System.ServiceModel.Primitives": "4.0.0",
+ "Thrift": "1.0.0-*",
+ //"Thrift": "1.0.0-*"
+ },
+
+ "frameworks": {
+ "netstandard1.6": {
+ "imports": "dnxcore50"
+ }
+ },
+
+ "scripts": {
+ "precompile": [
+ //"%project:Directory%/../../thrift.exe -r -out %project:Directory% --gen netcore:wcf %project:Directory%/tutorial.thrift"
+ ]
+ }
+}
diff --git a/tutorial/netcore/Makefile.am b/tutorial/netcore/Makefile.am
new file mode 100644
index 000000000..a3abaeeb5
--- /dev/null
+++ b/tutorial/netcore/Makefile.am
@@ -0,0 +1,82 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+SUBDIRS = .
+
+THRIFT = $(top_builddir)/compiler/cpp/thrift
+
+GENDIR = Interfaces/gen-netcore
+
+# Due to a known issue with "dotnet restore" the Thrift.dll dependency cannot be resolved from cmdline.
+# The problem does NOT affect Visual Studio builds, only cmdline.
+# - For details see https://github.com/dotnet/cli/issues/3199 and related tickets.
+# - Workaround is to temporarily copy the Thrift project into the solution
+COPYCMD = cp -u -p -r
+
+
+THRIFTCODE = \
+ Interfaces/Properties/AssemblyInfo.cs \
+ Client/Properties/AssemblyInfo.cs \
+ Client/Program.cs \
+ Server/Properties/AssemblyInfo.cs \
+ Server/Program.cs
+
+all-local: \
+ Client.exe
+
+Client.exe: $(THRIFTCODE)
+ $(MKDIR_P) $(GENDIR)
+ $(THRIFT) -gen netcore:wcf -r -out $(GENDIR) $(top_srcdir)/tutorial/tutorial.thrift
+ $(MKDIR_P) ./Thrift
+ $(COPYCMD) $(top_srcdir)/lib/netcore/Thrift/* ./Thrift
+ $(DOTNETCORE) --info
+ $(DOTNETCORE) restore
+ $(DOTNETCORE) build **/*/project.json -r win10-x64
+ $(DOTNETCORE) build **/*/project.json -r osx.10.11-x64
+ $(DOTNETCORE) build **/*/project.json -r ubuntu.16.04-x64
+
+clean-local:
+ $(RM) Client.exe
+ $(RM) Server.exe
+ $(RM) Interfaces.dll
+ $(RM) -r $(GENDIR)
+ $(RM) -r Client/bin
+ $(RM) -r Client/obj
+ $(RM) -r Server/bin
+ $(RM) -r Server/obj
+ $(RM) -r Interfaces/bin
+ $(RM) -r Interfaces/obj
+ $(RM) -r Thrift
+
+EXTRA_DIST = \
+ $(THRIFTCODE) \
+ global.json \
+ Tutorial.sln \
+ Interfaces/project.json \
+ Interfaces/Interfaces.xproj \
+ Server/project.json \
+ Server/Server.xproj \
+ Server/ThriftTest.pfx \
+ Client/project.json \
+ Client/Client.xproj \
+ Client/ThriftTest.pfx \
+ build.cmd \
+ build.sh \
+ README.md
+
diff --git a/tutorial/netcore/README.md b/tutorial/netcore/README.md
new file mode 100644
index 000000000..18aac02a3
--- /dev/null
+++ b/tutorial/netcore/README.md
@@ -0,0 +1,253 @@
+# Building of samples for different platforms
+
+Details:
+
+- [https://docs.microsoft.com/en-us/dotnet/articles/core/deploying/index ](https://docs.microsoft.com/en-us/dotnet/articles/core/deploying/index "https://docs.microsoft.com/en-us/dotnet/articles/core/deploying/index ")
+- [https://docs.microsoft.com/en-us/dotnet/articles/core/rid-catalog](https://docs.microsoft.com/en-us/dotnet/articles/core/rid-catalog "https://docs.microsoft.com/en-us/dotnet/articles/core/rid-catalog")
+
+# Running of samples
+
+Please install Thrift C# .NET Core library or copy sources and build them to correcly build and run samples
+
+# NetCore Server
+
+Usage:
+
+ Server.exe -h
+ will diplay help information
+
+ Server.exe -t:<transport> -p:<protocol>
+ will run server with specified arguments (tcp transport and binary protocol by default)
+
+Options:
+
+ -t (transport):
+ tcp - (default) tcp transport will be used (host - ""localhost"", port - 9090)
+ tcpbuffered - tcp buffered transport will be used (host - ""localhost"", port - 9090)
+ namedpipe - namedpipe transport will be used (pipe address - "".test"")
+ http - http transport will be used (http address - ""localhost:9090"")
+ tcptls - tcp transport with tls will be used (host - ""localhost"", port - 9090)
+
+ -p (protocol):
+ binary - (default) binary protocol will be used
+ compact - compact protocol will be used
+ json - json protocol will be used
+
+Sample:
+
+ Server.exe -t:tcp
+
+**Remarks**:
+
+ For TcpTls mode certificate's file ThriftTest.pfx should be in directory with binaries in case of command line usage (or at project level in case of debugging from IDE).
+ Password for certificate - "ThriftTest".
+
+
+
+# NetCore Client
+
+Usage:
+
+ Client.exe -h
+ will diplay help information
+
+ Client.exe -t:<transport> -p:<protocol>
+ will run client with specified arguments (tcp transport and binary protocol by default)
+
+Options:
+
+ -t (transport):
+ tcp - (default) tcp transport will be used (host - ""localhost"", port - 9090)
+ tcpbuffered - buffered transport over tcp will be used (host - ""localhost"", port - 9090)
+ namedpipe - namedpipe transport will be used (pipe address - "".test"")
+ http - http transport will be used (address - ""http://localhost:9090"")
+ tcptls - tcp tls transport will be used (host - ""localhost"", port - 9090)
+
+ -p (protocol):
+ binary - (default) binary protocol will be used
+ compact - compact protocol will be used
+ json - json protocol will be used
+
+Sample:
+
+ Client.exe -t:tcp -p:binary
+
+Remarks:
+
+ For TcpTls mode certificate's file ThriftTest.pfx should be in directory
+ with binaries in case of command line usage (or at project level in case of debugging from IDE).
+ Password for certificate - "ThriftTest".
+
+# How to test communication between NetCore and Python
+
+* Generate code with the latest **thrift.exe** util
+* Ensure that **thrift.exe** util generated folder **gen-py** with generated code for Python
+* Create **client.py** and **server.py** from the code examples below and save them to the folder with previosly generated folder **gen-py**
+* Run netcore samples (client and server) and python samples (client and server)
+
+Remarks:
+
+Samples of client and server code below use correct methods (operations)
+and fields (properties) according to generated contracts from *.thrift files
+
+At Windows 10 add record **127.0.0.1 testserver** to **C:\Windows\System32\drivers\etc\hosts** file
+for correct work of python server
+
+
+**Python Client:**
+
+```python
+import sys
+import glob
+sys.path.append('gen-py')
+
+from tutorial import Calculator
+from tutorial.ttypes import InvalidOperation, Operation, Work
+
+from thrift import Thrift
+from thrift.transport import TSocket
+from thrift.transport import TTransport
+from thrift.protocol import TBinaryProtocol
+
+
+def main():
+ # Make socket
+ transport = TSocket.TSocket('127.0.0.1', 9090)
+
+ # Buffering is critical. Raw sockets are very slow
+ transport = TTransport.TBufferedTransport(transport)
+
+ # Wrap in a protocol
+ protocol = TBinaryProtocol.TBinaryProtocol(transport)
+
+ # Create a client to use the protocol encoder
+ client = Calculator.Client(protocol)
+
+ # Connect!
+ transport.open()
+
+ client.Ping()
+ print('ping()')
+
+ sum = client.Add(1, 1)
+ print(('1+1=%d' % (sum)))
+
+ work = Work()
+
+ work.Op = Operation.Divide
+ work.Num1 = 1
+ work.Num2 = 0
+
+ try:
+ quotient = client.Calculate(1, work)
+ print('Whoa? You know how to divide by zero?')
+ print('FYI the answer is %d' % quotient)
+ except InvalidOperation as e:
+ print(('InvalidOperation: %r' % e))
+
+ work.Op = Operation.Substract
+ work.Num1 = 15
+ work.Num2 = 10
+
+ diff = client.Calculate(1, work)
+ print(('15-10=%d' % (diff)))
+
+ log = client.GetStruct(1)
+ print(('Check log: %s' % (log.Value)))
+
+ client.Zip()
+ print('zip()')
+
+ # Close!
+ transport.close()
+
+if __name__ == '__main__':
+ try:
+ main()
+ except Thrift.TException as tx:
+ print('%s' % tx.message)
+```
+
+
+**Python Server:**
+
+
+```python
+import glob
+import sys
+sys.path.append('gen-py')
+
+from tutorial import Calculator
+from tutorial.ttypes import InvalidOperation, Operation
+
+from shared.ttypes import SharedStruct
+
+from thrift.transport import TSocket
+from thrift.transport import TTransport
+from thrift.protocol import TBinaryProtocol
+from thrift.server import TServer
+
+
+class CalculatorHandler:
+ def __init__(self):
+ self.log = {}
+
+ def Ping(self):
+ print('ping()')
+
+ def Add(self, n1, n2):
+ print('add(%d,%d)' % (n1, n2))
+ return n1 + n2
+
+ def Calculate(self, logid, work):
+ print('calculate(%d, %r)' % (logid, work))
+
+ if work.Op == Operation.Add:
+ val = work.Num1 + work.Num2
+ elif work.Op == Operation.Substract:
+ val = work.Num1 - work.Num2
+ elif work.Op == Operation.Multiply:
+ val = work.Num1 * work.Num2
+ elif work.Op == Operation.Divide:
+ if work.Num2 == 0:
+ x = InvalidOperation()
+ x.WhatOp = work.Op
+ x.Why = 'Cannot divide by 0'
+ raise x
+ val = work.Num1 / work.Num2
+ else:
+ x = InvalidOperation()
+ x.WhatOp = work.Op
+ x.Why = 'Invalid operation'
+ raise x
+
+ log = SharedStruct()
+ log.Key = logid
+ log.Value = '%d' % (val)
+ self.log[logid] = log
+
+ return val
+
+ def GetStruct(self, key):
+ print('getStruct(%d)' % (key))
+ return self.log[key]
+
+ def Zip(self):
+ print('zip()')
+
+if __name__ == '__main__':
+ handler = CalculatorHandler()
+ processor = Calculator.Processor(handler)
+ transport = TSocket.TServerSocket(host="testserver", port=9090)
+ tfactory = TTransport.TBufferedTransportFactory()
+ pfactory = TBinaryProtocol.TBinaryProtocolFactory()
+
+ server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)
+ print('Starting the server...')
+ server.serve()
+ print('done.')
+
+ # You could do one of these for a multithreaded server
+ # server = TServer.TThreadedServer(processor, transport, tfactory, pfactory)
+ # server = TServer.TThreadPoolServer(processor, transport, tfactory, pfactory)
+``` \ No newline at end of file
diff --git a/tutorial/netcore/Server/Program.cs b/tutorial/netcore/Server/Program.cs
new file mode 100644
index 000000000..604192402
--- /dev/null
+++ b/tutorial/netcore/Server/Program.cs
@@ -0,0 +1,397 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Thrift;
+using Thrift.Protocols;
+using Thrift.Server;
+using Thrift.Transports;
+using Thrift.Transports.Server;
+using tutorial;
+using shared;
+
+namespace Server
+{
+ public class Program
+ {
+ private static readonly ILogger Logger = new LoggerFactory().CreateLogger(nameof(Server));
+
+ public static void Main(string[] args)
+ {
+ args = args ?? new string[0];
+
+ if (args.Any(x => x.StartsWith("-h", StringComparison.OrdinalIgnoreCase)))
+ {
+ DisplayHelp();
+ return;
+ }
+
+ using (var source = new CancellationTokenSource())
+ {
+ RunAsync(args, source.Token).GetAwaiter().GetResult();
+
+ Logger.LogInformation("Press any key to stop...");
+
+ Console.ReadLine();
+ source.Cancel();
+ }
+ }
+
+ private static void DisplayHelp()
+ {
+ Console.WriteLine(@"
+Usage:
+ Server.exe -h
+ will diplay help information
+
+ Server.exe -t:<transport> -p:<protocol>
+ will run server with specified arguments (tcp transport and binary protocol by default)
+
+Options:
+ -t (transport):
+ tcp - (default) tcp transport will be used (host - ""localhost"", port - 9090)
+ tcpbuffered - tcp buffered transport will be used (host - ""localhost"", port - 9090)
+ namedpipe - namedpipe transport will be used (pipe address - "".test"")
+ http - http transport will be used (http address - ""localhost:9090"")
+ tcptls - tcp transport with tls will be used (host - ""localhost"", port - 9090)
+
+ -p (protocol):
+ binary - (default) binary protocol will be used
+ compact - compact protocol will be used
+ json - json protocol will be used
+
+Sample:
+ Server.exe -t:tcp
+");
+ }
+
+ private static async Task RunAsync(string[] args, CancellationToken cancellationToken)
+ {
+ var selectedTransport = GetTransport(args);
+ var selectedProtocol = GetProtocol(args);
+
+ if (selectedTransport == Transport.Http)
+ {
+ new HttpServerSample().Run(cancellationToken);
+ }
+ else
+ {
+ await
+ RunSelectedConfigurationAsync(selectedTransport, selectedProtocol,
+ cancellationToken);
+ }
+ }
+
+ private static Protocol GetProtocol(string[] args)
+ {
+ var transport = args.FirstOrDefault(x => x.StartsWith("-p"))?.Split(':')?[1];
+ Protocol selectedProtocol;
+
+ Enum.TryParse(transport, true, out selectedProtocol);
+
+ return selectedProtocol;
+ }
+
+ private static Transport GetTransport(string[] args)
+ {
+ var transport = args.FirstOrDefault(x => x.StartsWith("-t"))?.Split(':')?[1];
+ Transport selectedTransport;
+
+ Enum.TryParse(transport, true, out selectedTransport);
+
+ return selectedTransport;
+ }
+
+ private static async Task RunSelectedConfigurationAsync(Transport transport,
+ Protocol protocol, CancellationToken cancellationToken)
+ {
+ var fabric = new LoggerFactory();
+ var handler = new CalculatorAsyncHandler();
+ var processor = new Calculator.AsyncProcessor(handler);
+
+ TServerTransport serverTransport = null;
+
+ switch (transport)
+ {
+ case Transport.Tcp:
+ serverTransport = new TServerSocketTransport(9090);
+ break;
+ case Transport.TcpBuffered:
+ serverTransport = new TServerSocketTransport(port: 9090, clientTimeout: 10000,
+ useBufferedSockets: true);
+ break;
+ case Transport.NamedPipe:
+ serverTransport = new TNamedPipeServerTransport(".test");
+ break;
+ case Transport.TcpTls:
+ serverTransport = new TTlsServerSocketTransport(9090, false, GetCertificate(),
+ ClientCertValidator, LocalCertificateSelectionCallback);
+ break;
+ }
+
+ ITProtocolFactory inputProtocolFactory;
+ ITProtocolFactory outputProtocolFactory;
+
+ switch (protocol)
+ {
+ case Protocol.Binary:
+ {
+ inputProtocolFactory = new TBinaryProtocol.Factory();
+ outputProtocolFactory = new TBinaryProtocol.Factory();
+ }
+ break;
+ case Protocol.Compact:
+ {
+ inputProtocolFactory = new TCompactProtocol.Factory();
+ outputProtocolFactory = new TCompactProtocol.Factory();
+ }
+ break;
+ case Protocol.Json:
+ {
+ inputProtocolFactory = new TJsonProtocol.Factory();
+ outputProtocolFactory = new TJsonProtocol.Factory();
+ }
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(protocol), protocol, null);
+ }
+
+ try
+ {
+ Logger.LogInformation(
+ $"Selected TAsyncServer with {serverTransport} transport and {inputProtocolFactory} protocol factories");
+
+ var server = new AsyncBaseServer(processor, serverTransport, inputProtocolFactory,
+ outputProtocolFactory, fabric);
+
+ Logger.LogInformation("Starting the server...");
+ await server.ServeAsync(cancellationToken);
+ }
+ catch (Exception x)
+ {
+ Logger.LogInformation(x.ToString());
+ }
+
+ Logger.LogInformation("Server stopped.");
+ }
+
+ private static X509Certificate2 GetCertificate()
+ {
+ // due to files location in net core better to take certs from top folder
+ var certFile = GetCertPath(Directory.GetParent(Directory.GetCurrentDirectory()));
+ return new X509Certificate2(certFile, "ThriftTest");
+ }
+
+ private static string GetCertPath(DirectoryInfo di, int maxCount = 6)
+ {
+ var topDir = di;
+ var certFile =
+ topDir.EnumerateFiles("ThriftTest.pfx", SearchOption.AllDirectories)
+ .FirstOrDefault();
+ if (certFile == null)
+ {
+ if (maxCount == 0)
+ throw new FileNotFoundException("Cannot find file in directories");
+ return GetCertPath(di.Parent, maxCount - 1);
+ }
+
+ return certFile.FullName;
+ }
+
+ private static X509Certificate LocalCertificateSelectionCallback(object sender,
+ string targetHost, X509CertificateCollection localCertificates,
+ X509Certificate remoteCertificate, string[] acceptableIssuers)
+ {
+ return GetCertificate();
+ }
+
+ private static bool ClientCertValidator(object sender, X509Certificate certificate,
+ X509Chain chain, SslPolicyErrors sslPolicyErrors)
+ {
+ return true;
+ }
+
+ private enum Transport
+ {
+ Tcp,
+ TcpBuffered,
+ NamedPipe,
+ Http,
+ TcpTls
+ }
+
+ private enum Protocol
+ {
+ Binary,
+ Compact,
+ Json,
+ }
+
+ public class HttpServerSample
+ {
+ public void Run(CancellationToken cancellationToken)
+ {
+ var config = new ConfigurationBuilder()
+ .AddEnvironmentVariables(prefix: "ASPNETCORE_")
+ .Build();
+
+ var host = new WebHostBuilder()
+ .UseConfiguration(config)
+ .UseKestrel()
+ .UseUrls("http://localhost:9090")
+ .UseContentRoot(Directory.GetCurrentDirectory())
+ .UseIISIntegration()
+ .UseStartup<Startup>()
+ .Build();
+
+ host.Run(cancellationToken);
+ }
+
+ public class Startup
+ {
+ public Startup(IHostingEnvironment env)
+ {
+ var builder = new ConfigurationBuilder()
+ .SetBasePath(env.ContentRootPath)
+ .AddEnvironmentVariables();
+
+ Configuration = builder.Build();
+ }
+
+ public IConfigurationRoot Configuration { get; }
+
+ // This method gets called by the runtime. Use this method to add services to the container.
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddTransient<Calculator.IAsync, CalculatorAsyncHandler>();
+ services.AddTransient<ITAsyncProcessor, Calculator.AsyncProcessor>();
+ services.AddTransient<THttpServerTransport, THttpServerTransport>();
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env,
+ ILoggerFactory loggerFactory)
+ {
+ app.UseMiddleware<THttpServerTransport>();
+ }
+ }
+ }
+
+ public class CalculatorAsyncHandler : Calculator.IAsync
+ {
+ Dictionary<int, SharedStruct> _log;
+
+ public CalculatorAsyncHandler()
+ {
+ _log = new Dictionary<int, SharedStruct>();
+ }
+
+ public async Task<SharedStruct> getStructAsync(int key,
+ CancellationToken cancellationToken)
+ {
+ Logger.LogInformation("GetStructAsync({0})", key);
+ return await Task.FromResult(_log[key]);
+ }
+
+ public async Task pingAsync(CancellationToken cancellationToken)
+ {
+ Logger.LogInformation("PingAsync()");
+ await Task.CompletedTask;
+ }
+
+ public async Task<int> addAsync(int num1, int num2, CancellationToken cancellationToken)
+ {
+ Logger.LogInformation($"AddAsync({num1},{num2})");
+ return await Task.FromResult(num1 + num2);
+ }
+
+ public async Task<int> calculateAsync(int logid, Work w, CancellationToken cancellationToken)
+ {
+ Logger.LogInformation($"CalculateAsync({logid}, [{w.Op},{w.Num1},{w.Num2}])");
+
+ var val = 0;
+ switch (w.Op)
+ {
+ case Operation.ADD:
+ val = w.Num1 + w.Num2;
+ break;
+
+ case Operation.SUBTRACT:
+ val = w.Num1 - w.Num2;
+ break;
+
+ case Operation.MULTIPLY:
+ val = w.Num1*w.Num2;
+ break;
+
+ case Operation.DIVIDE:
+ if (w.Num2 == 0)
+ {
+ var io = new InvalidOperation
+ {
+ WhatOp = (int) w.Op,
+ Why = "Cannot divide by 0"
+ };
+
+ throw io;
+ }
+ val = w.Num1/w.Num2;
+ break;
+
+ default:
+ {
+ var io = new InvalidOperation
+ {
+ WhatOp = (int) w.Op,
+ Why = "Unknown operation"
+ };
+
+ throw io;
+ }
+ }
+
+ var entry = new SharedStruct
+ {
+ Key = logid,
+ Value = val.ToString()
+ };
+
+ _log[logid] = entry;
+
+ return await Task.FromResult(val);
+ }
+
+ public async Task zipAsync(CancellationToken cancellationToken)
+ {
+ Logger.LogInformation("ZipAsync() with delay 100mc");
+ await Task.Delay(100, CancellationToken.None);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/tutorial/netcore/Server/Properties/AssemblyInfo.cs b/tutorial/netcore/Server/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..a0442350b
--- /dev/null
+++ b/tutorial/netcore/Server/Properties/AssemblyInfo.cs
@@ -0,0 +1,40 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("The Apache Software Foundation")]
+[assembly: AssemblyProduct("Thrift")]
+[assembly: AssemblyCopyright("The Apache Software Foundation")]
+[assembly: AssemblyTrademark("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+
+[assembly: Guid("e210fc10-5aff-4b04-ac21-58afc7b74b0c")] \ No newline at end of file
diff --git a/tutorial/netcore/Server/Properties/launchSettings.json b/tutorial/netcore/Server/Properties/launchSettings.json
new file mode 100644
index 000000000..e23253ddf
--- /dev/null
+++ b/tutorial/netcore/Server/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "Server": {
+ "commandName": "Project",
+ "commandLineArgs": "-t:tcptls"
+ }
+ }
+} \ No newline at end of file
diff --git a/tutorial/netcore/Server/Server.xproj b/tutorial/netcore/Server/Server.xproj
new file mode 100644
index 000000000..5cebad120
--- /dev/null
+++ b/tutorial/netcore/Server/Server.xproj
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
+ <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
+ </PropertyGroup>
+
+ <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>e210fc10-5aff-4b04-ac21-58afc7b74b0c</ProjectGuid>
+ <RootNamespace>Server</RootNamespace>
+ <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
+ <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
+ <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <SchemaVersion>2.0</SchemaVersion>
+ </PropertyGroup>
+ <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
+</Project>
diff --git a/tutorial/netcore/Server/ThriftTest.pfx b/tutorial/netcore/Server/ThriftTest.pfx
new file mode 100644
index 000000000..f0ded2817
--- /dev/null
+++ b/tutorial/netcore/Server/ThriftTest.pfx
Binary files differ
diff --git a/tutorial/netcore/Server/project.json b/tutorial/netcore/Server/project.json
new file mode 100644
index 000000000..7948c27f2
--- /dev/null
+++ b/tutorial/netcore/Server/project.json
@@ -0,0 +1,29 @@
+{
+ "version": "1.0.0-*",
+ "buildOptions": {
+ "debugType": "portable",
+ "emitEntryPoint": true
+ },
+
+ "dependencies": {
+ "Interfaces": "1.0.0-*",
+ "Microsoft.NETCore.App": {
+ "version": "1.0.0"
+ },
+ "Microsoft.AspNetCore.Http": "1.0.0",
+ "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
+ "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
+ "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0",
+ "Thrift": "1.0.0-*" },
+ "runtimes": {
+ "win10-x64": {},
+ "osx.10.11-x64": {},
+ "ubuntu.16.04-x64": {}
+ },
+
+ "frameworks": {
+ "netcoreapp1.0": {
+ "imports": "dnxcore50"
+ }
+ }
+}
diff --git a/tutorial/netcore/Tutorial.sln b/tutorial/netcore/Tutorial.sln
new file mode 100644
index 000000000..0368f21ae
--- /dev/null
+++ b/tutorial/netcore/Tutorial.sln
@@ -0,0 +1,45 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Server", "Server\Server.xproj", "{E210FC10-5AFF-4B04-AC21-58AFC7B74B0C}"
+EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Interfaces", "Interfaces\Interfaces.xproj", "{4D13163D-9067-4C9C-8AF0-64E08451397D}"
+EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Client", "Client\Client.xproj", "{DE78A01B-F7C6-49D1-97DA-669D2ED37641}"
+EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Thrift", "..\..\lib\netcore\Thrift\Thrift.xproj", "{6850CF46-5467-4C65-BD78-871581C539FC}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{49B45AE5-C6CB-4E11-AA74-6A5472FFAF8F}"
+ ProjectSection(SolutionItems) = preProject
+ global.json = global.json
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {E210FC10-5AFF-4B04-AC21-58AFC7B74B0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E210FC10-5AFF-4B04-AC21-58AFC7B74B0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E210FC10-5AFF-4B04-AC21-58AFC7B74B0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E210FC10-5AFF-4B04-AC21-58AFC7B74B0C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4D13163D-9067-4C9C-8AF0-64E08451397D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4D13163D-9067-4C9C-8AF0-64E08451397D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4D13163D-9067-4C9C-8AF0-64E08451397D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4D13163D-9067-4C9C-8AF0-64E08451397D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DE78A01B-F7C6-49D1-97DA-669D2ED37641}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DE78A01B-F7C6-49D1-97DA-669D2ED37641}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DE78A01B-F7C6-49D1-97DA-669D2ED37641}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DE78A01B-F7C6-49D1-97DA-669D2ED37641}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6850CF46-5467-4C65-BD78-871581C539FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6850CF46-5467-4C65-BD78-871581C539FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6850CF46-5467-4C65-BD78-871581C539FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6850CF46-5467-4C65-BD78-871581C539FC}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/tutorial/netcore/build.cmd b/tutorial/netcore/build.cmd
new file mode 100644
index 000000000..2d20cbedc
--- /dev/null
+++ b/tutorial/netcore/build.cmd
@@ -0,0 +1,45 @@
+@echo off
+rem /*
+rem * Licensed to the Apache Software Foundation (ASF) under one
+rem * or more contributor license agreements. See the NOTICE file
+rem * distributed with this work for additional information
+rem * regarding copyright ownership. The ASF licenses this file
+rem * to you under the Apache License, Version 2.0 (the
+rem * "License"); you may not use this file except in compliance
+rem * with the License. You may obtain a copy of the License at
+rem *
+rem * http://www.apache.org/licenses/LICENSE-2.0
+rem *
+rem * Unless required by applicable law or agreed to in writing,
+rem * software distributed under the License is distributed on an
+rem * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+rem * KIND, either express or implied. See the License for the
+rem * specific language governing permissions and limitations
+rem * under the License.
+rem */
+setlocal
+
+cd Interfaces
+thrift -gen netcore:wcf -r ..\..\tutorial.thrift
+cd ..
+
+rem * Due to a known issue with "dotnet restore" the Thrift.dll dependency cannot be resolved from cmdline
+rem * For details see https://github.com/dotnet/cli/issues/3199 and related tickets
+rem * The problem does NOT affect Visual Studio builds.
+
+rem * workaround for "dotnet restore" issue
+xcopy ..\..\lib\netcore\Thrift .\Thrift /YSEI >NUL
+
+dotnet --info
+dotnet restore
+
+dotnet build **/*/project.json -r win10-x64
+dotnet build **/*/project.json -r osx.10.11-x64
+dotnet build **/*/project.json -r ubuntu.16.04-x64
+
+rem * workaround for "dotnet restore" issue
+del .\Thrift\* /Q /S >NUL
+rd .\Thrift /Q /S >NUL
+
+
+:eof
diff --git a/tutorial/netcore/build.sh b/tutorial/netcore/build.sh
new file mode 100644
index 000000000..38794551b
--- /dev/null
+++ b/tutorial/netcore/build.sh
@@ -0,0 +1,44 @@
+#!/usr/bin/env bash
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+#exit if any command fails
+#set -e
+
+cd Interfaces
+../../../compiler/cpp/thrift -gen netcore:wcf -r ../../tutorial.thrift
+cd ..
+
+
+# Due to a known issue with "dotnet restore" the Thrift.dll dependency cannot be resolved from cmdline
+# For details see https://github.com/dotnet/cli/issues/3199 and related tickets
+# The problem does NOT affect Visual Studio builds.
+
+# workaround for "dotnet restore" issue
+cp -u -p -r ..\..\lib\netcore\Thrift .\Thrift
+
+dotnet --info
+dotnet restore
+
+dotnet build **/*/project.json -r win10-x64
+dotnet build **/*/project.json -r osx.10.11-x64
+dotnet build **/*/project.json -r ubuntu.16.04-x64
+# workaround for "dotnet restore" issue
+rm -r .\Thrift
diff --git a/tutorial/netcore/global.json b/tutorial/netcore/global.json
new file mode 100644
index 000000000..53f181109
--- /dev/null
+++ b/tutorial/netcore/global.json
@@ -0,0 +1,3 @@
+{
+ "projects": [ "../../lib/netcore" ]
+} \ No newline at end of file
diff --git a/tutorial/shared.thrift b/tutorial/shared.thrift
index 386000b17..3cc1bb34e 100644
--- a/tutorial/shared.thrift
+++ b/tutorial/shared.thrift
@@ -29,6 +29,7 @@ namespace java shared
namespace perl shared
namespace php shared
namespace haxe shared
+namespace netcore shared
struct SharedStruct {
1: i32 key
diff --git a/tutorial/tutorial.thrift b/tutorial/tutorial.thrift
index c4a96f02c..f8c5320d9 100644
--- a/tutorial/tutorial.thrift
+++ b/tutorial/tutorial.thrift
@@ -69,6 +69,7 @@ namespace java tutorial
namespace php tutorial
namespace perl tutorial
namespace haxe tutorial
+namespace netcore tutorial
/**
* Thrift lets you do typedefs to get pretty names for your types. Standard