diff --git a/.gitignore b/.gitignore
index 3e759b75bf4..4d0f3d5af45 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@
*.user
*.userosscache
*.sln.docstates
+.vscode/solution-explorer
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
diff --git a/.vscode/launch.json b/.vscode/launch.json
index ddbfc5fa664..571c85e3511 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -18,6 +18,20 @@
"stopAtEntry": false,
"internalConsoleOptions": "openOnSessionStart"
},
+ {
+ "name": "Jaeger Exporter Tests (console)",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "build",
+ // If you have changed target frameworks, make sure to update the program path.
+ "program": "${workspaceFolder}/test/OpenTelemetry.Exporter.Jaeger.Tests/bin/Debug/netcoreapp2.2/OpenTelemetry.Exporter.Jaeger.Tests.dll",
+ "args": [],
+ "cwd": "${workspaceFolder}/test/OpenTelemetry.Exporter.Jaeger.Tests",
+ // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
+ "console": "internalConsole",
+ "stopAtEntry": false,
+ "internalConsoleOptions": "openOnSessionStart"
+ },
{
"name": ".NET Core Attach",
"type": "coreclr",
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index a914fef1305..2d4663d57e2 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -5,12 +5,16 @@
"tasks": [
{
"label": "build",
- "command": "dotnet build",
+ "command": "dotnet",
"type": "shell",
"group": "build",
"presentation": {
"reveal": "silent"
},
+ "args": [
+ "build",
+ "/property:GenerateFullPaths=true"
+ ],
"problemMatcher": "$msCompile"
}
]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fdcdd85aa11..89157c48a2d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ the release.
1. Modified the content of PrometheusExporterOptions from `Uri()` to `string`.
2. `HttpListener()` can support "+" as: hostname which listens on all ports.
3. Modified samples/TestPrometheus.cs to safely use the new implementation.
+ 4. Jaeger exporter implemented
- Copy from
[OpenCensus](http://github.com/census-instrumentation/opencensus-csharp) at
diff --git a/Directory.Build.targets b/Directory.Build.targets
index cff5023aa19..96d0176b091 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -1,6 +1,6 @@
-
+
All
diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln
index b76e95f5904..ab2ac06e63f 100644
--- a/OpenTelemetry.sln
+++ b/OpenTelemetry.sln
@@ -80,6 +80,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LoggingTracer.Demo.ConsoleA
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LoggingTracer.Demo.AspNetCore", "samples\LoggingTracer\LoggingTracer.Demo.AspNetCore\LoggingTracer.Demo.AspNetCore.csproj", "{1EB74FCE-55C5-476A-8BB0-C46B77FE1070}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTelemetry.Exporter.Jaeger", "src\OpenTelemetry.Exporter.Jaeger\OpenTelemetry.Exporter.Jaeger.csproj", "{8D47E3CF-9AE3-42FE-9084-FEB72D9AD769}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTelemetry.Exporter.Jaeger.Tests", "test\OpenTelemetry.Exporter.Jaeger.Tests\OpenTelemetry.Exporter.Jaeger.Tests.csproj", "{21E69213-72D5-453F-BD00-75EF36AC4965}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -170,12 +174,19 @@ Global
{1EB74FCE-55C5-476A-8BB0-C46B77FE1070}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1EB74FCE-55C5-476A-8BB0-C46B77FE1070}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1EB74FCE-55C5-476A-8BB0-C46B77FE1070}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8D47E3CF-9AE3-42FE-9084-FEB72D9AD769}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8D47E3CF-9AE3-42FE-9084-FEB72D9AD769}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8D47E3CF-9AE3-42FE-9084-FEB72D9AD769}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8D47E3CF-9AE3-42FE-9084-FEB72D9AD769}.Release|Any CPU.Build.0 = Release|Any CPU
+ {21E69213-72D5-453F-BD00-75EF36AC4965}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {21E69213-72D5-453F-BD00-75EF36AC4965}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {21E69213-72D5-453F-BD00-75EF36AC4965}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {21E69213-72D5-453F-BD00-75EF36AC4965}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
- {7CB2F02E-03FA-4FFF-89A5-C51F107623FD} = {61188153-47FB-4567-AC9B-79B2435853EB}
{F2F81E76-6A0E-466B-B673-EBBF1A9ED075} = {77C7929A-2EED-4AA6-8705-B5C443C8AA0F}
{6EC9DEC9-086F-4F29-BF3F-5FC7253829D5} = {E359BB2B-9AEC-497D-B321-7DF2450C3B8E}
{D9C14CDA-5182-4DEA-8606-98FF1A388BD3} = {E359BB2B-9AEC-497D-B321-7DF2450C3B8E}
diff --git a/README.md b/README.md
index bdf4fc21b85..13f3ad55a26 100644
--- a/README.md
+++ b/README.md
@@ -161,6 +161,42 @@ Outgoing http calls to Redis made usign StackExchange.Redis library can be autom
## OpenTelemetry QuickStart: exporting data
+### Using the Jaeger exporter
+
+The Jaeger exporter communicates to a Jaeger Agent through the compact thrift protocol on
+the Compact Thrift API port. You can configure the Jaeger exporter by following the directions below:
+
+1. [Get Jaeger][jaeger-get-started].
+2. Configure the `JaegerExporter`
+ - `ServiceName`: The name of your application or service.
+ - `AgengHost`: Usually `localhost` since an agent should
+ usually be running on the same machine as your application or service.
+ - `AgentPort`: The compact thrift protocol port of the Jaeger Agent (default `6831`)
+ - `MaxPacketSize`: The maximum size of each UDP packet that gets sent to the agent. (default `65000`)
+3. See the [sample][jaeger-sample] for an example of how to use the exporter.
+
+``` csharp
+var exporter = new JaegerExporter(
+ new JaegerExporterOptions
+ {
+ ServiceName = "tracing-to-jaeger-service",
+ AgentHost = host,
+ AgentPort = port,
+ },
+ Tracing.ExportComponent);
+
+exporter.Start();
+
+var span = tracer
+ .SpanBuilder("incoming request")
+ .SetSampler(Samplers.AlwaysSample)
+ .StartSpan();
+
+Thread.Sleep(TimeSpan.FromSeconds(1));
+span.End();
+```
+
+
### Using Zipkin exporter
Configure Zipkin exporter to see traces in Zipkin UI.
@@ -316,6 +352,7 @@ deprecate it for 18 months before removing it, if possible.
[up-for-grabs-issues]: https://github.com/open-telemetry/OpenTelemetry-dotnet/issues?q=is%3Aissue+is%3Aopen+label%3Aup-for-grabs
[good-first-issues]: https://github.com/open-telemetry/OpenTelemetry-dotnet/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22
[zipkin-get-started]: https://zipkin.io/pages/quickstart.html
+[jaeger-get-started]: https://www.jaegertracing.io/docs/1.13/getting-started/
[ai-get-started]: https://docs.microsoft.com/azure/application-insights
[stackdriver-trace-setup]: https://cloud.google.com/trace/docs/setup/
[stackdriver-monitoring-setup]: https://cloud.google.com/monitoring/api/enable-api
@@ -326,6 +363,7 @@ deprecate it for 18 months before removing it, if possible.
[ai-sample]: https://github.com/open-telemetry/opentelemetry-dotnet/blob/master/samples/Exporters/TestApplicationInsights.cs
[stackdriver-sample]: https://github.com/open-telemetry/opentelemetry-dotnet/blob/master/samples/Exporters/TestStackdriver.cs
[zipkin-sample]: https://github.com/open-telemetry/opentelemetry-dotnet/blob/master/samples/Exporters/TestZipkin.cs
+[jaeger-sample]: https://github.com/open-telemetry/opentelemetry-dotnet/blob/master/samples/Exporters/TestJaeger.cs
[prometheus-get-started]: https://prometheus.io/docs/introduction/first_steps/
[prometheus-sample]: https://github.com/open-telemetry/opentelemetry-dotnet/blob/master/samples/Exporters/TestPrometheus.cs
diff --git a/lib/Directory.Build.props b/lib/Directory.Build.props
new file mode 100644
index 00000000000..e3fa4661306
--- /dev/null
+++ b/lib/Directory.Build.props
@@ -0,0 +1,8 @@
+
+
+
+
+
+ True
+
+
\ No newline at end of file
diff --git a/lib/Directory.Build.targets b/lib/Directory.Build.targets
new file mode 100644
index 00000000000..347a97078b1
--- /dev/null
+++ b/lib/Directory.Build.targets
@@ -0,0 +1,10 @@
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
\ No newline at end of file
diff --git a/lib/Thrift/Collections/TCollections.cs b/lib/Thrift/Collections/TCollections.cs
new file mode 100644
index 00000000000..147bfc7d31c
--- /dev/null
+++ b/lib/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
+ {
+ ///
+ /// 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.
+ ///
+ 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;
+ }
+
+ ///
+ /// This returns a hashcode based on the value of the enumerable.
+ ///
+ 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/Thrift/Collections/THashSet.cs b/lib/Thrift/Collections/THashSet.cs
new file mode 100644
index 00000000000..011f0a0d629
--- /dev/null
+++ b/lib/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 : ICollection
+ {
+ private readonly HashSet _set = new HashSet();
+
+ 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 IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable) _set).GetEnumerator();
+ }
+
+ public bool Remove(T item)
+ {
+ return _set.Remove(item);
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/Thrift/ITAsyncProcessor.cs b/lib/Thrift/ITAsyncProcessor.cs
new file mode 100644
index 00000000000..db8e40aefe8
--- /dev/null
+++ b/lib/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 ProcessAsync(TProtocol iprot, TProtocol oprot);
+ Task ProcessAsync(TProtocol iprot, TProtocol oprot, CancellationToken cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/lib/Thrift/ITProcessorFactory.cs b/lib/Thrift/ITProcessorFactory.cs
new file mode 100644
index 00000000000..5133e5c4801
--- /dev/null
+++ b/lib/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/Thrift/Protocols/Entities/TField.cs b/lib/Thrift/Protocols/Entities/TField.cs
new file mode 100644
index 00000000000..d311535e73c
--- /dev/null
+++ b/lib/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/Thrift/Protocols/Entities/TList.cs b/lib/Thrift/Protocols/Entities/TList.cs
new file mode 100644
index 00000000000..ce232207cba
--- /dev/null
+++ b/lib/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/Thrift/Protocols/Entities/TMap.cs b/lib/Thrift/Protocols/Entities/TMap.cs
new file mode 100644
index 00000000000..9195593db27
--- /dev/null
+++ b/lib/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/Thrift/Protocols/Entities/TMessage.cs b/lib/Thrift/Protocols/Entities/TMessage.cs
new file mode 100644
index 00000000000..17f49298f77
--- /dev/null
+++ b/lib/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/Thrift/Protocols/Entities/TMessageType.cs b/lib/Thrift/Protocols/Entities/TMessageType.cs
new file mode 100644
index 00000000000..d7b9a227595
--- /dev/null
+++ b/lib/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/Thrift/Protocols/Entities/TSet.cs b/lib/Thrift/Protocols/Entities/TSet.cs
new file mode 100644
index 00000000000..a583b54a681
--- /dev/null
+++ b/lib/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/Thrift/Protocols/Entities/TStruct.cs b/lib/Thrift/Protocols/Entities/TStruct.cs
new file mode 100644
index 00000000000..a28dcc3d001
--- /dev/null
+++ b/lib/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/Thrift/Protocols/Entities/TType.cs b/lib/Thrift/Protocols/Entities/TType.cs
new file mode 100644
index 00000000000..ebe781c9547
--- /dev/null
+++ b/lib/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/Thrift/Protocols/ITProtocolFactory.cs b/lib/Thrift/Protocols/ITProtocolFactory.cs
new file mode 100644
index 00000000000..ecc5cc494d4
--- /dev/null
+++ b/lib/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/Thrift/Protocols/TAbstractBase.cs b/lib/Thrift/Protocols/TAbstractBase.cs
new file mode 100644
index 00000000000..4e18681bfb9
--- /dev/null
+++ b/lib/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/Thrift/Protocols/TBase.cs b/lib/Thrift/Protocols/TBase.cs
new file mode 100644
index 00000000000..014e1aee856
--- /dev/null
+++ b/lib/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/Thrift/Protocols/TBinaryProtocol.cs b/lib/Thrift/Protocols/TBinaryProtocol.cs
new file mode 100644
index 00000000000..72bc3d2cdca
--- /dev/null
+++ b/lib/Thrift/Protocols/TBinaryProtocol.cs
@@ -0,0 +1,613 @@
+// 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).ConfigureAwait(false);
+ await WriteStringAsync(message.Name, cancellationToken).ConfigureAwait(false);
+ await WriteI32Async(message.SeqID, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ await WriteStringAsync(message.Name, cancellationToken).ConfigureAwait(false);
+ await WriteByteAsync((sbyte) message.Type, cancellationToken).ConfigureAwait(false);
+ await WriteI32Async(message.SeqID, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task WriteMessageEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task WriteStructBeginAsync(TStruct @struct, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task WriteStructEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task WriteFieldBeginAsync(TField field, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await WriteByteAsync((sbyte) field.Type, cancellationToken).ConfigureAwait(false);
+ await WriteI16Async(field.ID, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteFieldEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task WriteFieldStopAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await WriteByteAsync((sbyte) TType.Stop, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteMapBeginAsync(TMap map, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await WriteByteAsync((sbyte) map.KeyType, cancellationToken).ConfigureAwait(false);
+ await WriteByteAsync((sbyte) map.ValueType, cancellationToken).ConfigureAwait(false);
+ await WriteI32Async(map.Count, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteMapEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task WriteListBeginAsync(TList list, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await WriteByteAsync((sbyte) list.ElementType, cancellationToken).ConfigureAwait(false);
+ await WriteI32Async(list.Count, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteListEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task WriteSetBeginAsync(TSet set, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await WriteByteAsync((sbyte) set.ElementType, cancellationToken).ConfigureAwait(false);
+ await WriteI32Async(set.Count, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteSetEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task WriteBoolAsync(bool b, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await WriteByteAsync(b ? (sbyte) 1 : (sbyte) 0, cancellationToken).ConfigureAwait(false);
+ }
+
+ 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).ConfigureAwait(false);
+ }
+
+ 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).ConfigureAwait(false);
+ }
+
+ 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).ConfigureAwait(false);
+ }
+
+ 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).ConfigureAwait(false);
+ }
+
+ public override async Task WriteDoubleAsync(double d, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await WriteI64Async(BitConverter.DoubleToInt64Bits(d), cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteBinaryAsync(byte[] bytes, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await WriteI32Async(bytes.Length, cancellationToken).ConfigureAwait(false);
+ await Trans.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadMessageBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ var message = new TMessage();
+ var size = await ReadI32Async(cancellationToken).ConfigureAwait(false);
+ 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).ConfigureAwait(false);
+ message.SeqID = await ReadI32Async(cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ if (StrictRead)
+ {
+ throw new TProtocolException(TProtocolException.BAD_VERSION,
+ "Missing version in ReadMessageBegin, old client?");
+ }
+ message.Name = await ReadStringBodyAsync(size, cancellationToken).ConfigureAwait(false);
+ message.Type = (TMessageType) await ReadByteAsync(cancellationToken).ConfigureAwait(false);
+ message.SeqID = await ReadI32Async(cancellationToken).ConfigureAwait(false);
+ }
+ return message;
+ }
+
+ public override async Task ReadMessageEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task ReadStructBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ //TODO: no read from internal transport?
+ return new TStruct();
+ }
+
+ public override async Task ReadStructEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task ReadFieldBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ var field = new TField
+ {
+ Type = (TType) await ReadByteAsync(cancellationToken)
+ };
+
+ if (field.Type != TType.Stop)
+ {
+ field.ID = await ReadI16Async(cancellationToken).ConfigureAwait(false);
+ }
+
+ return field;
+ }
+
+ public override async Task ReadFieldEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task ReadMapBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ 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).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task ReadListBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ 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).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task ReadSetBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ 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).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task ReadBoolAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ return (await ReadByteAsync(cancellationToken).ConfigureAwait(false)) == 1;
+ }
+
+ public override async Task ReadByteAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ var bin = new byte[1];
+ await Trans.ReadAllAsync(bin, 0, 1, cancellationToken).ConfigureAwait(false); //TODO: why readall ?
+ return (sbyte) bin[0];
+ }
+
+ public override async Task ReadI16Async(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ var i16In = new byte[2];
+ await Trans.ReadAllAsync(i16In, 0, 2, cancellationToken).ConfigureAwait(false);
+ var result = (short) (((i16In[0] & 0xff) << 8) | i16In[1] & 0xff);
+ return result;
+ }
+
+ public override async Task ReadI32Async(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ var i32In = new byte[4];
+ await Trans.ReadAllAsync(i32In, 0, 4, cancellationToken).ConfigureAwait(false);
+
+ 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 ReadI64Async(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ var i64In = new byte[8];
+ await Trans.ReadAllAsync(i64In, 0, 8, cancellationToken).ConfigureAwait(false);
+ return CreateReadI64(i64In);
+ }
+
+ public override async Task ReadDoubleAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ var d = await ReadI64Async(cancellationToken).ConfigureAwait(false);
+ return BitConverter.Int64BitsToDouble(d);
+ }
+
+ public override async Task ReadBinaryAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ var size = await ReadI32Async(cancellationToken).ConfigureAwait(false);
+ var buf = new byte[size];
+ await Trans.ReadAllAsync(buf, 0, size, cancellationToken).ConfigureAwait(false);
+ return buf;
+ }
+
+ private async Task ReadStringBodyAsync(int size, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ var buf = new byte[size];
+ await Trans.ReadAllAsync(buf, 0, size, cancellationToken).ConfigureAwait(false);
+ 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);
+ }
+ }
+ }
+}
diff --git a/lib/Thrift/Protocols/TCompactProtocol.cs b/lib/Thrift/Protocols/TCompactProtocol.cs
new file mode 100644
index 00000000000..a9101075403
--- /dev/null
+++ b/lib/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];
+
+ ///
+ /// Used to keep track of the last field for the current and previous structs, so we can do the delta stuff.
+ ///
+ private readonly Stack _lastField = new Stack(15);
+
+ ///
+ /// If we encounter a boolean field begin, save the TField here so it can have the value incorporated.
+ ///
+ private TField? _booleanField;
+
+ ///
+ /// If we Read a field header, and it's a boolean field, save the boolean value here so that ReadBool can use it.
+ ///
+ 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).ConfigureAwait(false);
+ 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).ConfigureAwait(false);
+
+ await WriteStringAsync(message.Name, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteMessageEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// 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.
+ ///
+ public override async Task WriteStructBeginAsync(TStruct @struct, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ _lastField.Push(_lastFieldId);
+ _lastFieldId = 0;
+ }
+
+ public override async Task WriteStructEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ _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).ConfigureAwait(false);
+ }
+ else
+ {
+ // Write them separate
+ await Trans.WriteAsync(new[] {typeToWrite}, cancellationToken).ConfigureAwait(false);
+ await WriteI16Async(field.ID, cancellationToken).ConfigureAwait(false);
+ }
+
+ _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).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task WriteFieldEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task WriteFieldStopAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await Trans.WriteAsync(new[] {Types.Stop}, cancellationToken).ConfigureAwait(false);
+ }
+
+ 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).ConfigureAwait(false);
+ }
+ else
+ {
+ await Trans.WriteAsync(new[] {(byte) (0xf0 | GetCompactType(elemType))}, cancellationToken).ConfigureAwait(false);
+
+ var bufferTuple = CreateWriteVarInt32((uint) size);
+ await Trans.WriteAsync(bufferTuple.Item1, 0, bufferTuple.Item2, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task WriteListBeginAsync(TList list, CancellationToken cancellationToken)
+ {
+ await WriteCollectionBeginAsync(list.ElementType, list.Count, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteListEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task WriteSetBeginAsync(TSet set, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await WriteCollectionBeginAsync(set.ElementType, set.Count, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteSetEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ 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).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task WriteByteAsync(sbyte b, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await Trans.WriteAsync(new[] {(byte) b}, cancellationToken).ConfigureAwait(false);
+ }
+
+ 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).ConfigureAwait(false);
+ }
+
+ protected internal Tuple 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(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).ConfigureAwait(false);
+ }
+
+ protected internal Tuple 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(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).ConfigureAwait(false);
+ }
+
+ 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).ConfigureAwait(false);
+ }
+
+ 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).ConfigureAwait(false);
+ await Trans.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteBinaryAsync(byte[] bytes, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ var bufferTuple = CreateWriteVarInt32((uint) bytes.Length);
+ await Trans.WriteAsync(bufferTuple.Item1, 0, bufferTuple.Item2, cancellationToken).ConfigureAwait(false);
+ await Trans.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteMapBeginAsync(TMap map, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ if (map.Count == 0)
+ {
+ await Trans.WriteAsync(new[] {(byte) 0}, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ var bufferTuple = CreateWriteVarInt32((uint) map.Count);
+ await Trans.WriteAsync(bufferTuple.Item1, 0, bufferTuple.Item2, cancellationToken).ConfigureAwait(false);
+ 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).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task ReadMessageBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ var protocolId = (byte) await ReadByteAsync(cancellationToken).ConfigureAwait(false);
+ if (protocolId != ProtocolId)
+ {
+ throw new TProtocolException($"Expected protocol id {ProtocolId:X} but got {protocolId:X}");
+ }
+
+ var versionAndType = (byte) await ReadByteAsync(cancellationToken).ConfigureAwait(false);
+ 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).ConfigureAwait(false);
+ var messageName = await ReadStringAsync(cancellationToken).ConfigureAwait(false);
+
+ return new TMessage(messageName, (TMessageType) type, seqid);
+ }
+
+ public override async Task ReadMessageEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task ReadStructBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ // 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).ConfigureAwait(false);
+ }
+
+ /*
+ 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 ReadFieldBeginAsync(CancellationToken cancellationToken)
+ {
+ // Read a field header off the wire.
+ var type = (byte) await ReadByteAsync(cancellationToken).ConfigureAwait(false);
+ // 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).ConfigureAwait(false);
+ }
+ 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).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task ReadMapBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ /*
+ 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).ConfigureAwait(false);
+ var keyAndValueType = size == 0 ? (byte) 0 : (byte) await ReadByteAsync(cancellationToken).ConfigureAwait(false);
+ 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).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task 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).ConfigureAwait(false));
+ }
+
+ public override async Task ReadBoolAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ /*
+ 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).ConfigureAwait(false)) == Types.BooleanTrue;
+ }
+
+ public override async Task ReadByteAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ // Read a single byte off the wire. Nothing interesting here.
+ var buf = new byte[1];
+ await Trans.ReadAllAsync(buf, 0, 1, cancellationToken).ConfigureAwait(false);
+ return (sbyte) buf[0];
+ }
+
+ public override async Task ReadI16Async(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ return (short) ZigzagToInt(await ReadVarInt32Async(cancellationToken).ConfigureAwait(false));
+ }
+
+ public override async Task ReadI32Async(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ return ZigzagToInt(await ReadVarInt32Async(cancellationToken).ConfigureAwait(false));
+ }
+
+ public override async Task ReadI64Async(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ return ZigzagToLong(await ReadVarInt64Async(cancellationToken).ConfigureAwait(false));
+ }
+
+ public override async Task ReadDoubleAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ var longBits = new byte[8];
+ await Trans.ReadAllAsync(longBits, 0, 8, cancellationToken).ConfigureAwait(false);
+
+ return BitConverter.Int64BitsToDouble(BytesToLong(longBits));
+ }
+
+ public override async Task ReadStringAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ // Reads a byte[] (via ReadBinary), and then UTF-8 decodes it.
+ var length = (int) await ReadVarInt32Async(cancellationToken).ConfigureAwait(false);
+
+ if (length == 0)
+ {
+ return string.Empty;
+ }
+
+ var buf = new byte[length];
+ await Trans.ReadAllAsync(buf, 0, length, cancellationToken).ConfigureAwait(false);
+
+ return Encoding.UTF8.GetString(buf);
+ }
+
+ public override async Task ReadBinaryAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ // Read a byte[] from the wire.
+ var length = (int) await ReadVarInt32Async(cancellationToken).ConfigureAwait(false);
+ if (length == 0)
+ {
+ return new byte[0];
+ }
+
+ var buf = new byte[length];
+ await Trans.ReadAllAsync(buf, 0, length, cancellationToken).ConfigureAwait(false);
+ return buf;
+ }
+
+ public override async Task ReadListBeginAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ /*
+ 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).ConfigureAwait(false);
+ var size = (sizeAndType >> 4) & 0x0f;
+ if (size == 15)
+ {
+ size = (int) await ReadVarInt32Async(cancellationToken).ConfigureAwait(false);
+ }
+
+ var type = GetTType(sizeAndType);
+ return new TList(type, size);
+ }
+
+ public override async Task ReadListEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task ReadSetEndAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private static byte GetCompactType(TType ttype)
+ {
+ // Given a TType value, find the appropriate TCompactProtocol.Types constant.
+ return TTypeToCompactType[(int) ttype];
+ }
+
+
+ private async Task ReadVarInt32Async(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ /*
+ 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).ConfigureAwait(false);
+ result |= (uint) (b & 0x7f) << shift;
+ if ((b & 0x80) != 0x80)
+ {
+ break;
+ }
+ shift += 7;
+ }
+
+ return result;
+ }
+
+ private async Task ReadVarInt64Async(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ /*
+ 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).ConfigureAwait(false);
+ 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);
+ }
+ }
+
+ ///
+ /// All of the on-wire exType codes.
+ ///
+ 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;
+ }
+ }
+}
diff --git a/lib/Thrift/Protocols/TJSONProtocol.cs b/lib/Thrift/Protocols/TJSONProtocol.cs
new file mode 100644
index 00000000000..b2b5df2d9f2
--- /dev/null
+++ b/lib/Thrift/Protocols/TJSONProtocol.cs
@@ -0,0 +1,981 @@
+// 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.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Thrift.Protocols.Entities;
+using Thrift.Protocols.Utilities;
+using Thrift.Transports;
+
+namespace Thrift.Protocols
+{
+ ///
+ /// 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.
+ ///
+ // ReSharper disable once InconsistentNaming
+ public class TJsonProtocol : TProtocol
+ {
+ private const long Version = 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 ContextStack = new Stack();
+
+ // Reader that manages a 1-byte buffer
+ protected LookaheadReader Reader;
+
+ // Default encoding
+ protected Encoding Utf8Encoding = Encoding.UTF8;
+
+ ///
+ /// TJsonProtocol Constructor
+ ///
+ public TJsonProtocol(TClientTransport trans)
+ : base(trans)
+ {
+ Context = new JSONBaseContext(this);
+ Reader = new LookaheadReader(this);
+ }
+
+ ///
+ /// Push a new JSON context onto the stack.
+ ///
+ protected void PushContext(JSONBaseContext c)
+ {
+ ContextStack.Push(Context);
+ Context = c;
+ }
+
+ ///
+ /// Pop the last JSON context off the stack
+ ///
+ protected void PopContext()
+ {
+ Context = ContextStack.Pop();
+ }
+
+ ///
+ /// 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
+ ///
+ protected async Task ReadJsonSyntaxCharAsync(byte[] bytes, CancellationToken cancellationToken)
+ {
+ var ch = await Reader.ReadAsync(cancellationToken).ConfigureAwait(false);
+ if (ch != bytes[0])
+ {
+ throw new TProtocolException(TProtocolException.INVALID_DATA, $"Unexpected character: {(char) ch}");
+ }
+ }
+
+ ///
+ /// Write the bytes in array buf as a JSON characters, escaping as needed
+ ///
+ private async Task WriteJsonStringAsync(byte[] bytes, CancellationToken cancellationToken)
+ {
+ await Context.WriteAsync(cancellationToken).ConfigureAwait(false);
+ await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken).ConfigureAwait(false);
+
+ var len = bytes.Length;
+ for (var i = 0; i < len; i++)
+ {
+ if ((bytes[i] & 0x00FF) >= 0x30)
+ {
+ if (bytes[i] == TJSONProtocolConstants.Backslash[0])
+ {
+ await Trans.WriteAsync(TJSONProtocolConstants.Backslash, cancellationToken).ConfigureAwait(false);
+ await Trans.WriteAsync(TJSONProtocolConstants.Backslash, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ await Trans.WriteAsync(bytes.ToArray(), i, 1, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ else
+ {
+ _tempBuffer[0] = TJSONProtocolConstants.JsonCharTable[bytes[i]];
+ if (_tempBuffer[0] == 1)
+ {
+ await Trans.WriteAsync(bytes, i, 1, cancellationToken).ConfigureAwait(false);
+ }
+ else if (_tempBuffer[0] > 1)
+ {
+ await Trans.WriteAsync(TJSONProtocolConstants.Backslash, cancellationToken).ConfigureAwait(false);
+ await Trans.WriteAsync(_tempBuffer, 0, 1, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ await Trans.WriteAsync(TJSONProtocolConstants.EscSequences, cancellationToken).ConfigureAwait(false);
+ _tempBuffer[0] = TJSONProtocolHelper.ToHexChar((byte) (bytes[i] >> 4));
+ _tempBuffer[1] = TJSONProtocolHelper.ToHexChar(bytes[i]);
+ await Trans.WriteAsync(_tempBuffer, 0, 2, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ }
+ await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Write out number as a JSON value. If the context dictates so, it will be
+ /// wrapped in quotes to output as a JSON string.
+ ///
+ private async Task WriteJsonIntegerAsync(long num, CancellationToken cancellationToken)
+ {
+ await Context.WriteAsync(cancellationToken).ConfigureAwait(false);
+ var str = num.ToString();
+
+ var escapeNum = Context.EscapeNumbers();
+ if (escapeNum)
+ {
+ await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken).ConfigureAwait(false);
+ }
+
+ var bytes = Utf8Encoding.GetBytes(str);
+ await Trans.WriteAsync(bytes, cancellationToken).ConfigureAwait(false);
+
+ if (escapeNum)
+ {
+ await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// 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.
+ ///
+ private async Task WriteJsonDoubleAsync(double num, CancellationToken cancellationToken)
+ {
+ await Context.WriteAsync(cancellationToken).ConfigureAwait(false);
+ 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(TJSONProtocolConstants.Quote, cancellationToken).ConfigureAwait(false);
+ }
+
+ await Trans.WriteAsync(Utf8Encoding.GetBytes(str), cancellationToken).ConfigureAwait(false);
+
+ if (escapeNum)
+ {
+ await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Write out contents of byte array b as a JSON string with base-64 encoded
+ /// data
+ ///
+ private async Task WriteJsonBase64Async(byte[] bytes, CancellationToken cancellationToken)
+ {
+ await Context.WriteAsync(cancellationToken).ConfigureAwait(false);
+ await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken).ConfigureAwait(false);
+
+ var len = bytes.Length;
+ var off = 0;
+
+ while (len >= 3)
+ {
+ // Encode 3 bytes at a time
+ TBase64Helper.Encode(bytes, off, 3, _tempBuffer, 0);
+ await Trans.WriteAsync(_tempBuffer, 0, 4, cancellationToken).ConfigureAwait(false);
+ off += 3;
+ len -= 3;
+ }
+
+ if (len > 0)
+ {
+ // Encode remainder
+ TBase64Helper.Encode(bytes, off, len, _tempBuffer, 0);
+ await Trans.WriteAsync(_tempBuffer, 0, len + 1, cancellationToken).ConfigureAwait(false);
+ }
+
+ await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken).ConfigureAwait(false);
+ }
+
+ private async Task WriteJsonObjectStartAsync(CancellationToken cancellationToken)
+ {
+ await Context.WriteAsync(cancellationToken).ConfigureAwait(false);
+ await Trans.WriteAsync(TJSONProtocolConstants.LeftBrace, cancellationToken).ConfigureAwait(false);
+ PushContext(new JSONPairContext(this));
+ }
+
+ private async Task WriteJsonObjectEndAsync(CancellationToken cancellationToken)
+ {
+ PopContext();
+ await Trans.WriteAsync(TJSONProtocolConstants.RightBrace, cancellationToken).ConfigureAwait(false);
+ }
+
+ private async Task WriteJsonArrayStartAsync(CancellationToken cancellationToken)
+ {
+ await Context.WriteAsync(cancellationToken).ConfigureAwait(false);
+ await Trans.WriteAsync(TJSONProtocolConstants.LeftBracket, cancellationToken).ConfigureAwait(false);
+ PushContext(new JSONListContext(this));
+ }
+
+ private async Task WriteJsonArrayEndAsync(CancellationToken cancellationToken)
+ {
+ PopContext();
+ await Trans.WriteAsync(TJSONProtocolConstants.RightBracket, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteMessageBeginAsync(TMessage message, CancellationToken cancellationToken)
+ {
+ await WriteJsonArrayStartAsync(cancellationToken).ConfigureAwait(false);
+ await WriteJsonIntegerAsync(Version, cancellationToken).ConfigureAwait(false);
+
+ var b = Utf8Encoding.GetBytes(message.Name);
+ await WriteJsonStringAsync(b, cancellationToken).ConfigureAwait(false);
+
+ await WriteJsonIntegerAsync((long) message.Type, cancellationToken).ConfigureAwait(false);
+ await WriteJsonIntegerAsync(message.SeqID, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteMessageEndAsync(CancellationToken cancellationToken)
+ {
+ await WriteJsonArrayEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteStructBeginAsync(TStruct @struct, CancellationToken cancellationToken)
+ {
+ await WriteJsonObjectStartAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteStructEndAsync(CancellationToken cancellationToken)
+ {
+ await WriteJsonObjectEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteFieldBeginAsync(TField field, CancellationToken cancellationToken)
+ {
+ await WriteJsonIntegerAsync(field.ID, cancellationToken).ConfigureAwait(false);
+ await WriteJsonObjectStartAsync(cancellationToken).ConfigureAwait(false);
+ await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(field.Type), cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteFieldEndAsync(CancellationToken cancellationToken)
+ {
+ await WriteJsonObjectEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteFieldStopAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task WriteMapBeginAsync(TMap map, CancellationToken cancellationToken)
+ {
+ await WriteJsonArrayStartAsync(cancellationToken).ConfigureAwait(false);
+ await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(map.KeyType), cancellationToken).ConfigureAwait(false);
+ await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(map.ValueType), cancellationToken).ConfigureAwait(false);
+ await WriteJsonIntegerAsync(map.Count, cancellationToken).ConfigureAwait(false);
+ await WriteJsonObjectStartAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteMapEndAsync(CancellationToken cancellationToken)
+ {
+ await WriteJsonObjectEndAsync(cancellationToken).ConfigureAwait(false);
+ await WriteJsonArrayEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteListBeginAsync(TList list, CancellationToken cancellationToken)
+ {
+ await WriteJsonArrayStartAsync(cancellationToken).ConfigureAwait(false);
+ await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(list.ElementType), cancellationToken).ConfigureAwait(false);
+ await WriteJsonIntegerAsync(list.Count, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteListEndAsync(CancellationToken cancellationToken)
+ {
+ await WriteJsonArrayEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteSetBeginAsync(TSet set, CancellationToken cancellationToken)
+ {
+ await WriteJsonArrayStartAsync(cancellationToken).ConfigureAwait(false);
+ await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(set.ElementType), cancellationToken).ConfigureAwait(false);
+ await WriteJsonIntegerAsync(set.Count, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteSetEndAsync(CancellationToken cancellationToken)
+ {
+ await WriteJsonArrayEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteBoolAsync(bool b, CancellationToken cancellationToken)
+ {
+ await WriteJsonIntegerAsync(b ? 1 : 0, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteByteAsync(sbyte b, CancellationToken cancellationToken)
+ {
+ await WriteJsonIntegerAsync(b, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteI16Async(short i16, CancellationToken cancellationToken)
+ {
+ await WriteJsonIntegerAsync(i16, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteI32Async(int i32, CancellationToken cancellationToken)
+ {
+ await WriteJsonIntegerAsync(i32, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteI64Async(long i64, CancellationToken cancellationToken)
+ {
+ await WriteJsonIntegerAsync(i64, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteDoubleAsync(double d, CancellationToken cancellationToken)
+ {
+ await WriteJsonDoubleAsync(d, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteStringAsync(string s, CancellationToken cancellationToken)
+ {
+ var b = Utf8Encoding.GetBytes(s);
+ await WriteJsonStringAsync(b, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteBinaryAsync(byte[] bytes, CancellationToken cancellationToken)
+ {
+ await WriteJsonBase64Async(bytes, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Read in a JSON string, unescaping as appropriate.. Skip Reading from the
+ /// context if skipContext is true.
+ ///
+ private async Task ReadJsonStringAsync(bool skipContext, CancellationToken cancellationToken)
+ {
+ using (var buffer = new MemoryStream())
+ {
+ var codeunits = new List();
+
+
+ if (!skipContext)
+ {
+ await Context.ReadAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken).ConfigureAwait(false);
+
+ while (true)
+ {
+ var ch = await Reader.ReadAsync(cancellationToken).ConfigureAwait(false);
+ if (ch == TJSONProtocolConstants.Quote[0])
+ {
+ break;
+ }
+
+ // escaped?
+ if (ch != TJSONProtocolConstants.EscSequences[0])
+ {
+ await buffer.WriteAsync(new[] {ch}, 0, 1, cancellationToken).ConfigureAwait(false);
+ continue;
+ }
+
+ // distinguish between \uXXXX and \?
+ ch = await Reader.ReadAsync(cancellationToken).ConfigureAwait(false);
+ if (ch != TJSONProtocolConstants.EscSequences[1]) // control chars like \n
+ {
+ var off = Array.IndexOf(TJSONProtocolConstants.EscapeChars, (char) ch);
+ if (off == -1)
+ {
+ throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected control char");
+ }
+ ch = TJSONProtocolConstants.EscapeCharValues[off];
+ await buffer.WriteAsync(new[] {ch}, 0, 1, cancellationToken).ConfigureAwait(false);
+ continue;
+ }
+
+ // it's \uXXXX
+ await Trans.ReadAllAsync(_tempBuffer, 0, 4, cancellationToken).ConfigureAwait(false);
+
+ var wch = (short) ((TJSONProtocolHelper.ToHexVal(_tempBuffer[0]) << 12) +
+ (TJSONProtocolHelper.ToHexVal(_tempBuffer[1]) << 8) +
+ (TJSONProtocolHelper.ToHexVal(_tempBuffer[2]) << 4) +
+ TJSONProtocolHelper.ToHexVal(_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).ConfigureAwait(false);
+ codeunits.Clear();
+ }
+ else
+ {
+ var tmp = Utf8Encoding.GetBytes(new[] {(char) wch});
+ await buffer.WriteAsync(tmp, 0, tmp.Length, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ if (codeunits.Count > 0)
+ {
+ throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected low surrogate char");
+ }
+
+ return buffer.ToArray();
+ }
+ }
+
+ ///
+ /// 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.
+ ///
+ private async Task ReadJsonNumericCharsAsync(CancellationToken cancellationToken)
+ {
+ var strbld = new StringBuilder();
+ while (true)
+ {
+ //TODO: workaround for primitive types with TJsonProtocol, think - how to rewrite into more easy form without exceptions
+ try
+ {
+ var ch = await Reader.PeekAsync(cancellationToken).ConfigureAwait(false);
+ if (!TJSONProtocolHelper.IsJsonNumeric(ch))
+ {
+ break;
+ }
+ var c = (char)await Reader.ReadAsync(cancellationToken).ConfigureAwait(false);
+ strbld.Append(c);
+ }
+ catch (TTransportException)
+ {
+ break;
+ }
+ }
+ return strbld.ToString();
+ }
+
+ ///
+ /// Read in a JSON number. If the context dictates, Read in enclosing quotes.
+ ///
+ private async Task ReadJsonIntegerAsync(CancellationToken cancellationToken)
+ {
+ await Context.ReadAsync(cancellationToken).ConfigureAwait(false);
+ if (Context.EscapeNumbers())
+ {
+ await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken).ConfigureAwait(false);
+ }
+
+ var str = await ReadJsonNumericCharsAsync(cancellationToken).ConfigureAwait(false);
+ if (Context.EscapeNumbers())
+ {
+ await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken).ConfigureAwait(false);
+ }
+
+ try
+ {
+ return long.Parse(str);
+ }
+ catch (FormatException)
+ {
+ throw new TProtocolException(TProtocolException.INVALID_DATA, "Bad data encounted in numeric data");
+ }
+ }
+
+ ///
+ /// 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.
+ ///
+ private async Task ReadJsonDoubleAsync(CancellationToken cancellationToken)
+ {
+ await Context.ReadAsync(cancellationToken).ConfigureAwait(false);
+ if (await Reader.PeekAsync(cancellationToken) == TJSONProtocolConstants.Quote[0])
+ {
+ var arr = await ReadJsonStringAsync(true, cancellationToken).ConfigureAwait(false);
+ 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(TJSONProtocolConstants.Quote, cancellationToken).ConfigureAwait(false);
+ }
+
+ try
+ {
+ return double.Parse(await ReadJsonNumericCharsAsync(cancellationToken).ConfigureAwait(false), CultureInfo.InvariantCulture);
+ }
+ catch (FormatException)
+ {
+ throw new TProtocolException(TProtocolException.INVALID_DATA, "Bad data encounted in numeric data");
+ }
+ }
+
+ ///
+ /// Read in a JSON string containing base-64 encoded data and decode it.
+ ///
+ private async Task ReadJsonBase64Async(CancellationToken cancellationToken)
+ {
+ var b = await ReadJsonStringAsync(false, cancellationToken).ConfigureAwait(false);
+ 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
+ TBase64Helper.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
+ TBase64Helper.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).ConfigureAwait(false);
+ await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.LeftBrace, cancellationToken).ConfigureAwait(false);
+ PushContext(new JSONPairContext(this));
+ }
+
+ private async Task ReadJsonObjectEndAsync(CancellationToken cancellationToken)
+ {
+ await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.RightBrace, cancellationToken).ConfigureAwait(false);
+ PopContext();
+ }
+
+ private async Task ReadJsonArrayStartAsync(CancellationToken cancellationToken)
+ {
+ await Context.ReadAsync(cancellationToken).ConfigureAwait(false);
+ await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.LeftBracket, cancellationToken).ConfigureAwait(false);
+ PushContext(new JSONListContext(this));
+ }
+
+ private async Task ReadJsonArrayEndAsync(CancellationToken cancellationToken)
+ {
+ await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.RightBracket, cancellationToken).ConfigureAwait(false);
+ PopContext();
+ }
+
+ public override async Task ReadMessageBeginAsync(CancellationToken cancellationToken)
+ {
+ var message = new TMessage();
+ await ReadJsonArrayStartAsync(cancellationToken).ConfigureAwait(false);
+ if (await ReadJsonIntegerAsync(cancellationToken) != Version)
+ {
+ throw new TProtocolException(TProtocolException.BAD_VERSION, "Message contained bad version.");
+ }
+
+ var buf = await ReadJsonStringAsync(false, cancellationToken).ConfigureAwait(false);
+ message.Name = Utf8Encoding.GetString(buf, 0, buf.Length);
+ message.Type = (TMessageType) await ReadJsonIntegerAsync(cancellationToken).ConfigureAwait(false);
+ message.SeqID = (int) await ReadJsonIntegerAsync(cancellationToken).ConfigureAwait(false);
+ return message;
+ }
+
+ public override async Task ReadMessageEndAsync(CancellationToken cancellationToken)
+ {
+ await ReadJsonArrayEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadStructBeginAsync(CancellationToken cancellationToken)
+ {
+ await ReadJsonObjectStartAsync(cancellationToken).ConfigureAwait(false);
+ return new TStruct();
+ }
+
+ public override async Task ReadStructEndAsync(CancellationToken cancellationToken)
+ {
+ await ReadJsonObjectEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadFieldBeginAsync(CancellationToken cancellationToken)
+ {
+ var field = new TField();
+ var ch = await Reader.PeekAsync(cancellationToken).ConfigureAwait(false);
+ if (ch == TJSONProtocolConstants.RightBrace[0])
+ {
+ field.Type = TType.Stop;
+ }
+ else
+ {
+ field.ID = (short) await ReadJsonIntegerAsync(cancellationToken).ConfigureAwait(false);
+ await ReadJsonObjectStartAsync(cancellationToken).ConfigureAwait(false);
+ field.Type = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken).ConfigureAwait(false));
+ }
+ return field;
+ }
+
+ public override async Task ReadFieldEndAsync(CancellationToken cancellationToken)
+ {
+ await ReadJsonObjectEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadMapBeginAsync(CancellationToken cancellationToken)
+ {
+ var map = new TMap();
+ await ReadJsonArrayStartAsync(cancellationToken).ConfigureAwait(false);
+ map.KeyType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken).ConfigureAwait(false));
+ map.ValueType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken).ConfigureAwait(false));
+ map.Count = (int) await ReadJsonIntegerAsync(cancellationToken).ConfigureAwait(false);
+ await ReadJsonObjectStartAsync(cancellationToken).ConfigureAwait(false);
+ return map;
+ }
+
+ public override async Task ReadMapEndAsync(CancellationToken cancellationToken)
+ {
+ await ReadJsonObjectEndAsync(cancellationToken).ConfigureAwait(false);
+ await ReadJsonArrayEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadListBeginAsync(CancellationToken cancellationToken)
+ {
+ var list = new TList();
+ await ReadJsonArrayStartAsync(cancellationToken).ConfigureAwait(false);
+ list.ElementType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken).ConfigureAwait(false));
+ list.Count = (int) await ReadJsonIntegerAsync(cancellationToken).ConfigureAwait(false);
+ return list;
+ }
+
+ public override async Task ReadListEndAsync(CancellationToken cancellationToken)
+ {
+ await ReadJsonArrayEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadSetBeginAsync(CancellationToken cancellationToken)
+ {
+ var set = new TSet();
+ await ReadJsonArrayStartAsync(cancellationToken).ConfigureAwait(false);
+ set.ElementType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken).ConfigureAwait(false));
+ set.Count = (int) await ReadJsonIntegerAsync(cancellationToken).ConfigureAwait(false);
+ return set;
+ }
+
+ public override async Task ReadSetEndAsync(CancellationToken cancellationToken)
+ {
+ await ReadJsonArrayEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadBoolAsync(CancellationToken cancellationToken)
+ {
+ return (await ReadJsonIntegerAsync(cancellationToken).ConfigureAwait(false)) != 0;
+ }
+
+ public override async Task ReadByteAsync(CancellationToken cancellationToken)
+ {
+ return (sbyte) await ReadJsonIntegerAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadI16Async(CancellationToken cancellationToken)
+ {
+ return (short) await ReadJsonIntegerAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadI32Async(CancellationToken cancellationToken)
+ {
+ return (int) await ReadJsonIntegerAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadI64Async(CancellationToken cancellationToken)
+ {
+ return await ReadJsonIntegerAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadDoubleAsync(CancellationToken cancellationToken)
+ {
+ return await ReadJsonDoubleAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadStringAsync(CancellationToken cancellationToken)
+ {
+ var buf = await ReadJsonStringAsync(false, cancellationToken).ConfigureAwait(false);
+ return Utf8Encoding.GetString(buf, 0, buf.Length);
+ }
+
+ public override async Task ReadBinaryAsync(CancellationToken cancellationToken)
+ {
+ return await ReadJsonBase64Async(cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Factory for JSON protocol objects
+ ///
+ public class Factory : ITProtocolFactory
+ {
+ public TProtocol GetProtocol(TClientTransport trans)
+ {
+ return new TJsonProtocol(trans);
+ }
+ }
+
+ ///
+ /// Base class for tracking JSON contexts that may require
+ /// inserting/Reading additional JSON syntax characters
+ /// This base context does nothing.
+ ///
+ 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).ConfigureAwait(false);
+ }
+ }
+
+ public virtual async Task ReadAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public virtual bool EscapeNumbers()
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Context for JSON lists. Will insert/Read commas before each item except
+ /// for the first one
+ ///
+ 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(TJSONProtocolConstants.Comma, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public override async Task ReadAsync(CancellationToken cancellationToken)
+ {
+ if (_first)
+ {
+ _first = false;
+ }
+ else
+ {
+ await Proto.ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Comma, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ }
+
+ ///
+ /// 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).
+ ///
+ // ReSharper disable once InconsistentNaming
+ 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 ? TJSONProtocolConstants.Colon : TJSONProtocolConstants.Comma, cancellationToken).ConfigureAwait(false);
+ _colon = !_colon;
+ }
+ }
+
+ public override async Task ReadAsync(CancellationToken cancellationToken)
+ {
+ if (_first)
+ {
+ _first = false;
+ _colon = true;
+ }
+ else
+ {
+ await Proto.ReadJsonSyntaxCharAsync(_colon ? TJSONProtocolConstants.Colon : TJSONProtocolConstants.Comma, cancellationToken).ConfigureAwait(false);
+ _colon = !_colon;
+ }
+ }
+
+ public override bool EscapeNumbers()
+ {
+ return _colon;
+ }
+ }
+
+ ///
+ /// Holds up to one byte from the transport
+ ///
+ protected class LookaheadReader
+ {
+ private readonly byte[] _data = new byte[1];
+
+ private bool _hasData;
+ protected TJsonProtocol Proto;
+
+ public LookaheadReader(TJsonProtocol proto)
+ {
+ Proto = proto;
+ }
+
+ ///
+ /// 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.
+ ///
+ public async Task ReadAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ if (_hasData)
+ {
+ _hasData = false;
+ }
+ else
+ {
+ // find more easy way to avoid exception on reading primitive types
+ await Proto.Trans.ReadAllAsync(_data, 0, 1, cancellationToken).ConfigureAwait(false);
+ }
+ return _data[0];
+ }
+
+ ///
+ /// Return the next byte to be Read without consuming, filling the data
+ /// buffer if it has not been filled alReady.
+ ///
+ public async Task PeekAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ if (!_hasData)
+ {
+ // find more easy way to avoid exception on reading primitive types
+ await Proto.Trans.ReadAllAsync(_data, 0, 1, cancellationToken).ConfigureAwait(false);
+ }
+ _hasData = true;
+ return _data[0];
+ }
+ }
+ }
+}
diff --git a/lib/Thrift/Protocols/TMultiplexedProtocol.cs b/lib/Thrift/Protocols/TMultiplexedProtocol.cs
new file mode 100644
index 00000000000..38f1c6ca0d8
--- /dev/null
+++ b/lib/Thrift/Protocols/TMultiplexedProtocol.cs
@@ -0,0 +1,91 @@
+// 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 serviceName
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;
+ }
+
+ 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).ConfigureAwait(false);
+ break;
+ default:
+ await base.WriteMessageBeginAsync(message, cancellationToken).ConfigureAwait(false);
+ break;
+ }
+ }
+ }
+}
diff --git a/lib/Thrift/Protocols/TProtocol.cs b/lib/Thrift/Protocols/TProtocol.cs
new file mode 100644
index 00000000000..5bda512f9a3
--- /dev/null
+++ b/lib/Thrift/Protocols/TProtocol.cs
@@ -0,0 +1,376 @@
+// 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
+ {
+ public 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;
+
+ 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).ConfigureAwait(false);
+ }
+
+ public abstract Task WriteMessageBeginAsync(TMessage message, CancellationToken cancellationToken);
+
+ public virtual async Task WriteMessageEndAsync()
+ {
+ await WriteMessageEndAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task WriteMessageEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task WriteStructBeginAsync(TStruct @struct)
+ {
+ await WriteStructBeginAsync(@struct, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task WriteStructBeginAsync(TStruct @struct, CancellationToken cancellationToken);
+
+ public virtual async Task WriteStructEndAsync()
+ {
+ await WriteStructEndAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task WriteStructEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task WriteFieldBeginAsync(TField field)
+ {
+ await WriteFieldBeginAsync(field, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task WriteFieldBeginAsync(TField field, CancellationToken cancellationToken);
+
+ public virtual async Task WriteFieldEndAsync()
+ {
+ await WriteFieldEndAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task WriteFieldEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task WriteFieldStopAsync()
+ {
+ await WriteFieldStopAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task WriteFieldStopAsync(CancellationToken cancellationToken);
+
+ public virtual async Task WriteMapBeginAsync(TMap map)
+ {
+ await WriteMapBeginAsync(map, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task WriteMapBeginAsync(TMap map, CancellationToken cancellationToken);
+
+ public virtual async Task WriteMapEndAsync()
+ {
+ await WriteMapEndAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task WriteMapEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task WriteListBeginAsync(TList list)
+ {
+ await WriteListBeginAsync(list, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task WriteListBeginAsync(TList list, CancellationToken cancellationToken);
+
+ public virtual async Task WriteListEndAsync()
+ {
+ await WriteListEndAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task WriteListEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task WriteSetBeginAsync(TSet set)
+ {
+ await WriteSetBeginAsync(set, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task WriteSetBeginAsync(TSet set, CancellationToken cancellationToken);
+
+ public virtual async Task WriteSetEndAsync()
+ {
+ await WriteSetEndAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task WriteSetEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task WriteBoolAsync(bool b)
+ {
+ await WriteBoolAsync(b, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task WriteBoolAsync(bool b, CancellationToken cancellationToken);
+
+ public virtual async Task WriteByteAsync(sbyte b)
+ {
+ await WriteByteAsync(b, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task WriteByteAsync(sbyte b, CancellationToken cancellationToken);
+
+ public virtual async Task WriteI16Async(short i16)
+ {
+ await WriteI16Async(i16, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task WriteI16Async(short i16, CancellationToken cancellationToken);
+
+ public virtual async Task WriteI32Async(int i32)
+ {
+ await WriteI32Async(i32, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task WriteI32Async(int i32, CancellationToken cancellationToken);
+
+ public virtual async Task WriteI64Async(long i64)
+ {
+ await WriteI64Async(i64, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task WriteI64Async(long i64, CancellationToken cancellationToken);
+
+ public virtual async Task WriteDoubleAsync(double d)
+ {
+ await WriteDoubleAsync(d, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task WriteDoubleAsync(double d, CancellationToken cancellationToken);
+
+ public virtual async Task WriteStringAsync(string s)
+ {
+ await WriteStringAsync(s, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public virtual async Task WriteStringAsync(string s, CancellationToken cancellationToken)
+ {
+ var bytes = Encoding.UTF8.GetBytes(s);
+ await WriteBinaryAsync(bytes, cancellationToken).ConfigureAwait(false);
+ }
+
+ public virtual async Task WriteBinaryAsync(byte[] bytes)
+ {
+ await WriteBinaryAsync(bytes, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task WriteBinaryAsync(byte[] bytes, CancellationToken cancellationToken);
+
+ public virtual async Task ReadMessageBeginAsync()
+ {
+ return await ReadMessageBeginAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task ReadMessageBeginAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadMessageEndAsync()
+ {
+ await ReadMessageEndAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task ReadMessageEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadStructBeginAsync()
+ {
+ return await ReadStructBeginAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task ReadStructBeginAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadStructEndAsync()
+ {
+ await ReadStructEndAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task ReadStructEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadFieldBeginAsync()
+ {
+ return await ReadFieldBeginAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task ReadFieldBeginAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadFieldEndAsync()
+ {
+ await ReadFieldEndAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task ReadFieldEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadMapBeginAsync()
+ {
+ return await ReadMapBeginAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task ReadMapBeginAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadMapEndAsync()
+ {
+ await ReadMapEndAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task ReadMapEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadListBeginAsync()
+ {
+ return await ReadListBeginAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task ReadListBeginAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadListEndAsync()
+ {
+ await ReadListEndAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task ReadListEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadSetBeginAsync()
+ {
+ return await ReadSetBeginAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task ReadSetBeginAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadSetEndAsync()
+ {
+ await ReadSetEndAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task ReadSetEndAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadBoolAsync()
+ {
+ return await ReadBoolAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task ReadBoolAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadByteAsync()
+ {
+ return await ReadByteAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task ReadByteAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadI16Async()
+ {
+ return await ReadI16Async(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task ReadI16Async(CancellationToken cancellationToken);
+
+ public virtual async Task ReadI32Async()
+ {
+ return await ReadI32Async(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task ReadI32Async(CancellationToken cancellationToken);
+
+ public virtual async Task ReadI64Async()
+ {
+ return await ReadI64Async(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task ReadI64Async(CancellationToken cancellationToken);
+
+ public virtual async Task ReadDoubleAsync()
+ {
+ return await ReadDoubleAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task ReadDoubleAsync(CancellationToken cancellationToken);
+
+ public virtual async Task ReadStringAsync()
+ {
+ return await ReadStringAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public virtual async Task ReadStringAsync(CancellationToken cancellationToken)
+ {
+ var buf = await ReadBinaryAsync(cancellationToken).ConfigureAwait(false);
+ return Encoding.UTF8.GetString(buf, 0, buf.Length);
+ }
+
+ public virtual async Task ReadBinaryAsync()
+ {
+ return await ReadBinaryAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public abstract Task ReadBinaryAsync(CancellationToken cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/lib/Thrift/Protocols/TProtocolDecorator.cs b/lib/Thrift/Protocols/TProtocolDecorator.cs
new file mode 100644
index 00000000000..b1c47a9200e
--- /dev/null
+++ b/lib/Thrift/Protocols/TProtocolDecorator.cs
@@ -0,0 +1,247 @@
+// 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
+ ///
+ /// 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.
+ ///
+ public abstract class TProtocolDecorator : TProtocol
+ {
+ private readonly TProtocol _wrappedProtocol;
+
+ protected TProtocolDecorator(TProtocol protocol)
+ : base(protocol.Transport)
+ {
+ _wrappedProtocol = protocol ?? throw new ArgumentNullException(nameof(protocol));
+ }
+
+ public override async Task WriteMessageBeginAsync(TMessage message, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteMessageBeginAsync(message, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteMessageEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteMessageEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteStructBeginAsync(TStruct @struct, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteStructBeginAsync(@struct, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteStructEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteStructEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteFieldBeginAsync(TField field, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteFieldBeginAsync(field, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteFieldEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteFieldEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteFieldStopAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteFieldStopAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteMapBeginAsync(TMap map, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteMapBeginAsync(map, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteMapEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteMapEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteListBeginAsync(TList list, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteListBeginAsync(list, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteListEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteListEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteSetBeginAsync(TSet set, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteSetBeginAsync(set, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteSetEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteSetEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteBoolAsync(bool b, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteBoolAsync(b, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteByteAsync(sbyte b, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteByteAsync(b, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteI16Async(short i16, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteI16Async(i16, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteI32Async(int i32, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteI32Async(i32, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteI64Async(long i64, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteI64Async(i64, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteDoubleAsync(double d, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteDoubleAsync(d, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteStringAsync(string s, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteStringAsync(s, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task WriteBinaryAsync(byte[] bytes, CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.WriteBinaryAsync(bytes, cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadMessageBeginAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadMessageBeginAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadMessageEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.ReadMessageEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadStructBeginAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadStructBeginAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadStructEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.ReadStructEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadFieldBeginAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadFieldBeginAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadFieldEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.ReadFieldEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadMapBeginAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadMapBeginAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadMapEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.ReadMapEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadListBeginAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadListBeginAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadListEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.ReadListEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadSetBeginAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadSetBeginAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadSetEndAsync(CancellationToken cancellationToken)
+ {
+ await _wrappedProtocol.ReadSetEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadBoolAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadBoolAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadByteAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadByteAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadI16Async(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadI16Async(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadI32Async(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadI32Async(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadI64Async(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadI64Async(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadDoubleAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadDoubleAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadStringAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadStringAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override async Task ReadBinaryAsync(CancellationToken cancellationToken)
+ {
+ return await _wrappedProtocol.ReadBinaryAsync(cancellationToken).ConfigureAwait(false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/Thrift/Protocols/TProtocolException.cs b/lib/Thrift/Protocols/TProtocolException.cs
new file mode 100644
index 00000000000..8c67c3bfdf9
--- /dev/null
+++ b/lib/Thrift/Protocols/TProtocolException.cs
@@ -0,0 +1,59 @@
+// 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.
+
+// ReSharper disable InconsistentNaming
+namespace Thrift.Protocols
+{
+ public class TProtocolException : TException
+ {
+ // do not rename public constants - 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/Thrift/Protocols/Utilities/TBase64Helper.cs b/lib/Thrift/Protocols/Utilities/TBase64Helper.cs
new file mode 100644
index 00000000000..7eff5e181d1
--- /dev/null
+++ b/lib/Thrift/Protocols/Utilities/TBase64Helper.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 TBase64Helper
+ {
+ //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/Thrift/Protocols/Utilities/TBase64Utils.cs b/lib/Thrift/Protocols/Utilities/TBase64Utils.cs
new file mode 100644
index 00000000000..15fd45cbef9
--- /dev/null
+++ b/lib/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/Thrift/Protocols/Utilities/TJsonProtocolConstants.cs b/lib/Thrift/Protocols/Utilities/TJsonProtocolConstants.cs
new file mode 100644
index 00000000000..93eff785555
--- /dev/null
+++ b/lib/Thrift/Protocols/Utilities/TJsonProtocolConstants.cs
@@ -0,0 +1,61 @@
+// 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.Utilities
+{
+ // ReSharper disable once InconsistentNaming
+ public static class TJSONProtocolConstants
+ {
+ //TODO Check for performance for reusing ImmutableArray from System.Collections.Immutable (https://blogs.msdn.microsoft.com/dotnet/2013/06/24/please-welcome-immutablearrayt/)
+ // can be possible to get better performance and also better GC
+
+ public static readonly byte[] Comma = {(byte) ','};
+ public static readonly byte[] Colon = {(byte) ':'};
+ public static readonly byte[] LeftBrace = {(byte) '{'};
+ public static readonly byte[] RightBrace = {(byte) '}'};
+ public static readonly byte[] LeftBracket = {(byte) '['};
+ public static readonly byte[] RightBracket = {(byte) ']'};
+ public static readonly byte[] Quote = {(byte) '"'};
+ public static readonly byte[] Backslash = {(byte) '\\'};
+
+ public static 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
+ };
+
+ public static readonly char[] EscapeChars = "\"\\/bfnrt".ToCharArray();
+ public static readonly byte[] EscapeCharValues = {(byte) '"', (byte) '\\', (byte) '/', (byte) '\b', (byte) '\f', (byte) '\n', (byte) '\r', (byte) '\t'};
+ public static readonly byte[] EscSequences = {(byte) '\\', (byte) 'u', (byte) '0', (byte) '0'};
+
+ public static class TypeNames
+ {
+ public static readonly byte[] NameBool = { (byte)'t', (byte)'f' };
+ public static readonly byte[] NameByte = { (byte)'i', (byte)'8' };
+ public static readonly byte[] NameI16 = { (byte)'i', (byte)'1', (byte)'6' };
+ public static readonly byte[] NameI32 = { (byte)'i', (byte)'3', (byte)'2' };
+ public static readonly byte[] NameI64 = { (byte)'i', (byte)'6', (byte)'4' };
+ public static readonly byte[] NameDouble = { (byte)'d', (byte)'b', (byte)'l' };
+ public static readonly byte[] NameStruct = { (byte)'r', (byte)'e', (byte)'c' };
+ public static readonly byte[] NameString = { (byte)'s', (byte)'t', (byte)'r' };
+ public static readonly byte[] NameMap = { (byte)'m', (byte)'a', (byte)'p' };
+ public static readonly byte[] NameList = { (byte)'l', (byte)'s', (byte)'t' };
+ public static readonly byte[] NameSet = { (byte)'s', (byte)'e', (byte)'t' };
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/Thrift/Protocols/Utilities/TJsonProtocolHelper.cs b/lib/Thrift/Protocols/Utilities/TJsonProtocolHelper.cs
new file mode 100644
index 00000000000..adc26a9af01
--- /dev/null
+++ b/lib/Thrift/Protocols/Utilities/TJsonProtocolHelper.cs
@@ -0,0 +1,176 @@
+// 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.Protocols.Entities;
+
+namespace Thrift.Protocols.Utilities
+{
+ // ReSharper disable once InconsistentNaming
+ public static class TJSONProtocolHelper
+ {
+ public static byte[] GetTypeNameForTypeId(TType typeId)
+ {
+ switch (typeId)
+ {
+ case TType.Bool:
+ return TJSONProtocolConstants.TypeNames.NameBool;
+ case TType.Byte:
+ return TJSONProtocolConstants.TypeNames.NameByte;
+ case TType.I16:
+ return TJSONProtocolConstants.TypeNames.NameI16;
+ case TType.I32:
+ return TJSONProtocolConstants.TypeNames.NameI32;
+ case TType.I64:
+ return TJSONProtocolConstants.TypeNames.NameI64;
+ case TType.Double:
+ return TJSONProtocolConstants.TypeNames.NameDouble;
+ case TType.String:
+ return TJSONProtocolConstants.TypeNames.NameString;
+ case TType.Struct:
+ return TJSONProtocolConstants.TypeNames.NameStruct;
+ case TType.Map:
+ return TJSONProtocolConstants.TypeNames.NameMap;
+ case TType.Set:
+ return TJSONProtocolConstants.TypeNames.NameSet;
+ case TType.List:
+ return TJSONProtocolConstants.TypeNames.NameList;
+ default:
+ throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, "Unrecognized exType");
+ }
+ }
+
+ public 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;
+ }
+
+ ///
+ /// Return true if the given byte could be a valid part of a JSON number.
+ ///
+ public 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;
+ default:
+ return false;
+ }
+ }
+
+ ///
+ /// Convert a byte containing a hex char ('0'-'9' or 'a'-'f') into its
+ /// corresponding hex value
+ ///
+ public static byte ToHexVal(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");
+ }
+
+ ///
+ /// Convert a byte containing a hex value to its corresponding hex character
+ ///
+ public static byte ToHexChar(byte val)
+ {
+ val &= 0x0F;
+ if (val < 10)
+ {
+ return (byte)((char)val + '0');
+ }
+ val -= 10;
+ return (byte)((char)val + 'a');
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/Thrift/Protocols/Utilities/TProtocolUtil.cs b/lib/Thrift/Protocols/Utilities/TProtocolUtil.cs
new file mode 100644
index 00000000000..ffc466c8825
--- /dev/null
+++ b/lib/Thrift/Protocols/Utilities/TProtocolUtil.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.Threading;
+using System.Threading.Tasks;
+using Thrift.Protocols.Entities;
+
+namespace Thrift.Protocols.Utilities
+{
+ // ReSharper disable once InconsistentNaming
+ public static class TProtocolUtil
+ {
+ public static async Task SkipAsync(TProtocol protocol, TType type, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ await Task.FromCanceled(cancellationToken).ConfigureAwait(false);
+ }
+
+ protocol.IncrementRecursionDepth();
+ try
+ {
+ switch (type)
+ {
+ case TType.Bool:
+ await protocol.ReadBoolAsync(cancellationToken).ConfigureAwait(false);
+ break;
+ case TType.Byte:
+ await protocol.ReadByteAsync(cancellationToken).ConfigureAwait(false);
+ break;
+ case TType.I16:
+ await protocol.ReadI16Async(cancellationToken).ConfigureAwait(false);
+ break;
+ case TType.I32:
+ await protocol.ReadI32Async(cancellationToken).ConfigureAwait(false);
+ break;
+ case TType.I64:
+ await protocol.ReadI64Async(cancellationToken).ConfigureAwait(false);
+ break;
+ case TType.Double:
+ await protocol.ReadDoubleAsync(cancellationToken).ConfigureAwait(false);
+ break;
+ case TType.String:
+ // Don't try to decode the string, just skip it.
+ await protocol.ReadBinaryAsync(cancellationToken).ConfigureAwait(false);
+ break;
+ case TType.Struct:
+ await protocol.ReadStructBeginAsync(cancellationToken).ConfigureAwait(false);
+ while (true)
+ {
+ var field = await protocol.ReadFieldBeginAsync(cancellationToken).ConfigureAwait(false);
+ if (field.Type == TType.Stop)
+ {
+ break;
+ }
+ await SkipAsync(protocol, field.Type, cancellationToken).ConfigureAwait(false);
+ await protocol.ReadFieldEndAsync(cancellationToken).ConfigureAwait(false);
+ }
+ await protocol.ReadStructEndAsync(cancellationToken).ConfigureAwait(false);
+ break;
+ case TType.Map:
+ var map = await protocol.ReadMapBeginAsync(cancellationToken).ConfigureAwait(false);
+ for (var i = 0; i < map.Count; i++)
+ {
+ await SkipAsync(protocol, map.KeyType, cancellationToken).ConfigureAwait(false);
+ await SkipAsync(protocol, map.ValueType, cancellationToken).ConfigureAwait(false);
+ }
+ await protocol.ReadMapEndAsync(cancellationToken).ConfigureAwait(false);
+ break;
+ case TType.Set:
+ var set = await protocol.ReadSetBeginAsync(cancellationToken).ConfigureAwait(false);
+ for (var i = 0; i < set.Count; i++)
+ {
+ await SkipAsync(protocol, set.ElementType, cancellationToken).ConfigureAwait(false);
+ }
+ await protocol.ReadSetEndAsync(cancellationToken).ConfigureAwait(false);
+ break;
+ case TType.List:
+ var list = await protocol.ReadListBeginAsync(cancellationToken).ConfigureAwait(false);
+ for (var i = 0; i < list.Count; i++)
+ {
+ await SkipAsync(protocol, list.ElementType, cancellationToken).ConfigureAwait(false);
+ }
+ await protocol.ReadListEndAsync(cancellationToken).ConfigureAwait(false);
+ break;
+ default:
+ throw new TProtocolException(TProtocolException.INVALID_DATA, "Unknown data type " + type.ToString("d"));
+ }
+ }
+ finally
+ {
+ protocol.DecrementRecursionDepth();
+ }
+ }
+ }
+}
diff --git a/lib/Thrift/README.md b/lib/Thrift/README.md
new file mode 100644
index 00000000000..acd7ed2ceb9
--- /dev/null
+++ b/lib/Thrift/README.md
@@ -0,0 +1,12 @@
+# Thrift Protocol .Net Implementation
+
+This is the .net implementation of the Apache Thrift protocol. This code was forked from the [Jaeger Tracing C# Client Repository](https://github.com/jaegertracing/jaeger-client-csharp).
+
+Path: src/Thrift
+
+commitID: 0794ea71cb6e58f7bf0f0ef2c0c8ceceb1d8b6d9
+
+The following changes were made to this fork:
+
+* ConfigureAwait(false) added to async calls to prevent deadlocks.
+* THttpClientTransport uses WebRequestHandler() in .NET Framework 4.6 builds.
\ No newline at end of file
diff --git a/lib/Thrift/Server/TBaseServer.cs b/lib/Thrift/Server/TBaseServer.cs
new file mode 100644
index 00000000000..f7af8174850
--- /dev/null
+++ b/lib/Thrift/Server/TBaseServer.cs
@@ -0,0 +1,75 @@
+// 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;
+using Thrift.Transports;
+
+namespace Thrift.Server
+{
+ // ReSharper disable once InconsistentNaming
+ public abstract class TBaseServer
+ {
+ 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)
+ {
+ ItProcessorFactory = itProcessorFactory ?? throw new ArgumentNullException(nameof(itProcessorFactory));
+ ServerTransport = serverTransport;
+ InputTransportFactory = inputTransportFactory ?? throw new ArgumentNullException(nameof(inputTransportFactory));
+ OutputTransportFactory = outputTransportFactory ?? throw new ArgumentNullException(nameof(outputTransportFactory));
+ InputProtocolFactory = inputProtocolFactory ?? throw new ArgumentNullException(nameof(inputProtocolFactory));
+ OutputProtocolFactory = outputProtocolFactory ?? throw new ArgumentNullException(nameof(outputProtocolFactory));
+ }
+
+ 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).ConfigureAwait(false);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/Thrift/Server/TServerEventHandler.cs b/lib/Thrift/Server/TServerEventHandler.cs
new file mode 100644
index 00000000000..733bb4bef7c
--- /dev/null
+++ b/lib/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?
+
+ ///
+ /// Interface implemented by server users to handle events from the server
+ ///
+ // ReSharper disable once InconsistentNaming
+ public interface TServerEventHandler
+ {
+ ///
+ /// Called before the server begins */
+ ///
+ Task PreServeAsync(CancellationToken cancellationToken);
+
+ ///
+ /// Called when a new client has connected and is about to being processing */
+ ///
+ Task
diff --git a/samples/Exporters/Program.cs b/samples/Exporters/Program.cs
index b03c75bcbf9..1b7fb7b7d9d 100644
--- a/samples/Exporters/Program.cs
+++ b/samples/Exporters/Program.cs
@@ -35,8 +35,9 @@ public class Program
/// Arguments from command line.
public static void Main(string[] args)
{
- Parser.Default.ParseArguments(args)
+ Parser.Default.ParseArguments(args)
.MapResult(
+ (JaegerOptions options) => TestJaeger.Run(options.Host, options.Port),
(ZipkinOptions options) => TestZipkin.Run(options.Uri),
(ApplicationInsightsOptions options) => TestApplicationInsights.Run(),
(PrometheusOptions options) => TestPrometheus.Run(),
@@ -57,6 +58,16 @@ internal class StackdriverOptions
public string ProjectId { get; set; }
}
+ [Verb("jaeger", HelpText = "Specify the options required to test Jaeger exporter")]
+ internal class JaegerOptions
+ {
+ [Option('h', "host", HelpText = "Please specify the host of the Jaeger Agent", Required = true)]
+ public string Host { get; set; }
+
+ [Option('p', "port", HelpText = "Please specify the port of the Jaeger Agent", Required = true)]
+ public int Port { get; set; }
+ }
+
[Verb("zipkin", HelpText = "Specify the options required to test Zipkin exporter")]
internal class ZipkinOptions
{
diff --git a/samples/Exporters/TestJaeger.cs b/samples/Exporters/TestJaeger.cs
new file mode 100644
index 00000000000..941bd5b12e8
--- /dev/null
+++ b/samples/Exporters/TestJaeger.cs
@@ -0,0 +1,103 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace Samples
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Threading;
+ using OpenTelemetry.Exporter.Jaeger;
+ using OpenTelemetry.Trace;
+ using OpenTelemetry.Trace.Config;
+ using OpenTelemetry.Trace.Sampler;
+
+ internal class TestJaeger
+ {
+ internal static object Run(string host, int port)
+ {
+ // 1. Configure exporter to export traces to Jaeger
+ var exporter = new JaegerExporter(
+ new JaegerExporterOptions
+ {
+ ServiceName = "tracing-to-jaeger-service",
+ AgentHost = host,
+ AgentPort = port,
+ },
+ Tracing.ExportComponent);
+
+ exporter.Start();
+
+ // 2. Configure 100% sample rate for the purposes of the demo
+ ITraceConfig traceConfig = Tracing.TraceConfig;
+ ITraceParams currentConfig = traceConfig.ActiveTraceParams;
+ var newConfig = currentConfig.ToBuilder()
+ .SetSampler(Samplers.AlwaysSample)
+ .Build();
+ traceConfig.UpdateActiveTraceParams(newConfig);
+
+ // 3. Tracer is global singleton. You can register it via dependency injection if it exists
+ // but if not - you can use it as follows:
+ var tracer = Tracing.Tracer;
+
+ // 4. Create a scoped span. It will end automatically when using statement ends
+ using (tracer.WithSpan(tracer.SpanBuilder("Main").StartSpan()))
+ {
+ tracer.CurrentSpan.SetAttribute("custom-attribute", 55);
+ Console.WriteLine("About to do a busy work");
+ for (int i = 0; i < 10; i++)
+ {
+ DoWork(i);
+ }
+ }
+
+ // 5. Gracefully shutdown the exporter so it'll flush queued traces to Zipkin.
+ Tracing.ExportComponent.SpanExporter.Dispose();
+
+ return null;
+ }
+
+ private static void DoWork(int i)
+ {
+ // 6. Get the global singleton Tracer object
+ var tracer = Tracing.Tracer;
+
+ // 7. Start another span. If another span was already started, it'll use that span as the parent span.
+ // In this example, the main method already started a span, so that'll be the parent span, and this will be
+ // a child span.
+ using (tracer.WithSpan(tracer.SpanBuilder("DoWork").StartSpan()))
+ {
+ // Simulate some work.
+ var span = tracer.CurrentSpan;
+
+ try
+ {
+ Console.WriteLine("Doing busy work");
+ Thread.Sleep(1000);
+ }
+ catch (ArgumentOutOfRangeException e)
+ {
+ // 6. Set status upon error
+ span.Status = Status.Internal.WithDescription(e.ToString());
+ }
+
+ // 7. Annotate our span to capture metadata about our operation
+ var attributes = new Dictionary();
+ attributes.Add("use", "demo");
+ span.AddEvent("Invoking DoWork", attributes);
+ }
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/Batch.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/Batch.cs
new file mode 100644
index 00000000000..83d7b3b47dc
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/Batch.cs
@@ -0,0 +1,103 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Implementation
+{
+ using System;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Thrift.Protocols;
+ using Thrift.Protocols.Entities;
+ using Thrift.Protocols.Utilities;
+
+ public class Batch : TAbstractBase
+ {
+ public Batch()
+ {
+ }
+
+ public Batch(Process process, IEnumerable spans)
+ : this()
+ {
+ this.Process = process;
+ this.Spans = spans ?? Enumerable.Empty();
+ }
+
+ public Process Process { get; set; }
+
+ public IEnumerable Spans { get; set; }
+
+ public async Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken)
+ {
+ oprot.IncrementRecursionDepth();
+ try
+ {
+ var struc = new TStruct("Batch");
+
+ await oprot.WriteStructBeginAsync(struc, cancellationToken);
+
+ var field = new TField
+ {
+ Name = "process",
+ Type = TType.Struct,
+ ID = 1,
+ };
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await this.Process.WriteAsync(oprot, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+
+ field.Name = "spans";
+ field.Type = TType.List;
+ field.ID = 2;
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ {
+ await oprot.WriteListBeginAsync(new TList(TType.Struct, this.Spans.Count()), cancellationToken);
+ foreach (JaegerSpan s in this.Spans)
+ {
+ await s.WriteAsync(oprot, cancellationToken);
+ }
+
+ await oprot.WriteListEndAsync(cancellationToken);
+ }
+
+ await oprot.WriteFieldEndAsync(cancellationToken);
+ await oprot.WriteFieldStopAsync(cancellationToken);
+ await oprot.WriteStructEndAsync(cancellationToken);
+ }
+ finally
+ {
+ oprot.DecrementRecursionDepth();
+ }
+ }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder("Batch(");
+ sb.Append(", Process: ");
+ sb.Append(this.Process?.ToString() ?? "");
+ sb.Append(", Spans: ");
+ sb.Append(this.Spans);
+ sb.Append(")");
+ return sb.ToString();
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/EmitBatchArgs.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/EmitBatchArgs.cs
new file mode 100644
index 00000000000..aa794caca3b
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/EmitBatchArgs.cs
@@ -0,0 +1,78 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Implementation
+{
+ using System;
+ using System.Text;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Thrift.Protocols;
+ using Thrift.Protocols.Entities;
+
+ public class EmitBatchArgs : TAbstractBase
+ {
+ public EmitBatchArgs()
+ {
+ }
+
+ public Batch Batch { get; set; }
+
+ public async Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken)
+ {
+ oprot.IncrementRecursionDepth();
+ try
+ {
+ var struc = new TStruct("emitBatch_args");
+ await oprot.WriteStructBeginAsync(struc, cancellationToken);
+ if (this.Batch is Batch batch)
+ {
+ var field = new TField
+ {
+ Name = "batch",
+ Type = TType.Struct,
+ ID = 1,
+ };
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await batch.WriteAsync(oprot, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+ }
+
+ await oprot.WriteFieldStopAsync(cancellationToken);
+ await oprot.WriteStructEndAsync(cancellationToken);
+ }
+ finally
+ {
+ oprot.DecrementRecursionDepth();
+ }
+ }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder("emitBatch_args(");
+
+ if (this.Batch is Batch batch)
+ {
+ sb.Append("Batch: ");
+ sb.Append(batch?.ToString() ?? "");
+ }
+
+ sb.Append(")");
+ return sb.ToString();
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/IJaegerUdpBatcher.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/IJaegerUdpBatcher.cs
new file mode 100644
index 00000000000..fea9cbdcace
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/IJaegerUdpBatcher.cs
@@ -0,0 +1,31 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Implementation
+{
+ using System;
+ using System.Threading;
+ using System.Threading.Tasks;
+
+ public interface IJaegerUdpBatcher : IDisposable
+ {
+ Task AppendAsync(JaegerSpan span, CancellationToken cancellationToken);
+
+ Task CloseAsync(CancellationToken cancellationToken);
+
+ Task FlushAsync(CancellationToken cancellationToken);
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/IJaegerUdpClient.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/IJaegerUdpClient.cs
new file mode 100644
index 00000000000..b1a28617d8e
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/IJaegerUdpClient.cs
@@ -0,0 +1,35 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Implementation
+{
+ using System;
+ using System.Net;
+ using System.Threading.Tasks;
+
+ public interface IJaegerUdpClient : IDisposable
+ {
+ bool Connected { get; }
+
+ EndPoint RemoteEndPoint { get; }
+
+ void Connect(string host, int port);
+
+ void Close();
+
+ Task SendAsync(byte[] datagram, int bytes);
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/InMemoryTransport.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/InMemoryTransport.cs
new file mode 100644
index 00000000000..330dae04df5
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/InMemoryTransport.cs
@@ -0,0 +1,90 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Implementation
+{
+ using System;
+ using System.IO;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Thrift.Transports;
+
+ public class InMemoryTransport : TClientTransport
+ {
+ private readonly MemoryStream byteStream;
+ private bool isDisposed;
+
+ public InMemoryTransport()
+ {
+ this.byteStream = new MemoryStream();
+ }
+
+ public override bool IsOpen => true;
+
+ public override Task OpenAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+
+ return Task.CompletedTask;
+ }
+
+ public override void Close()
+ {
+ // do nothing
+ }
+
+ public override Task ReadAsync(
+ byte[] buffer,
+ int offset,
+ int length,
+ CancellationToken cancellationToken) => this.byteStream.ReadAsync(buffer, offset, length, cancellationToken);
+
+ public override Task WriteAsync(byte[] buffer, CancellationToken cancellationToken) => this.byteStream.WriteAsync(buffer, 0, buffer.Length, cancellationToken);
+
+ public override Task WriteAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken) => this.byteStream.WriteAsync(buffer, offset, length, cancellationToken);
+
+ public override Task FlushAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+
+ return Task.CompletedTask;
+ }
+
+ public byte[] GetBuffer() => this.byteStream.ToArray();
+
+ public void Reset() => this.byteStream.SetLength(0);
+
+ // IDisposable
+ protected override void Dispose(bool disposing)
+ {
+ if (!this.isDisposed)
+ {
+ if (disposing)
+ {
+ this.byteStream?.Dispose();
+ }
+ }
+
+ this.isDisposed = true;
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/Int128.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/Int128.cs
new file mode 100644
index 00000000000..99b5b827f42
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/Int128.cs
@@ -0,0 +1,53 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Implementation
+{
+ using System;
+ using System.Diagnostics;
+
+ public struct Int128
+ {
+ public static Int128 Empty = default;
+
+ private const int SpanIdBytes = 8;
+ private const int TraceIdBytes = 16;
+
+ public Int128(ActivitySpanId spanId)
+ {
+ var bytes = new byte[SpanIdBytes];
+
+ spanId.CopyTo(bytes);
+
+ this.High = 0;
+ this.Low = BitConverter.ToInt64(bytes, 0);
+ }
+
+ public Int128(ActivityTraceId traceId)
+ {
+ var bytes = new byte[TraceIdBytes];
+
+ traceId.CopyTo(bytes);
+
+ this.High = BitConverter.ToInt64(bytes, 0);
+ this.Low = BitConverter.ToInt64(bytes, 8);
+ }
+
+ public long High { get; set; }
+
+ public long Low { get; set; }
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerConversionExtensions.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerConversionExtensions.cs
new file mode 100644
index 00000000000..854244e4f90
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerConversionExtensions.cs
@@ -0,0 +1,145 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Implementation
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Linq;
+ using OpenTelemetry.Trace;
+ using OpenTelemetry.Trace.Export;
+
+ public static class JaegerConversionExtensions
+ {
+ private const int DaysPerYear = 365;
+
+ // Number of days in 4 years
+ private const int DaysPer4Years = (DaysPerYear * 4) + 1; // 1461
+
+ // Number of days in 100 years
+ private const int DaysPer100Years = (DaysPer4Years * 25) - 1; // 36524
+
+ // Number of days in 400 years
+ private const int DaysPer400Years = (DaysPer100Years * 4) + 1; // 146097
+
+ // Number of days from 1/1/0001 to 12/31/1969
+ private const int DaysTo1970 = (DaysPer400Years * 4) + (DaysPer100Years * 3) + (DaysPer4Years * 17) + DaysPerYear; // 719,162
+
+ private const long UnixEpochTicks = DaysTo1970 * TimeSpan.TicksPerDay;
+ private const long TicksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000;
+ private const long UnixEpochMicroseconds = UnixEpochTicks / TicksPerMicrosecond; // 62,135,596,800,000,000
+
+ public static JaegerSpan ToJaegerSpan(this SpanData span)
+ {
+ IEnumerable jaegerTags = null;
+
+ if (span?.Attributes?.AttributeMap is IEnumerable> attributeMap)
+ {
+ jaegerTags = attributeMap.Select(a => a.ToJaegerTag()).AsEnumerable();
+ }
+
+ IEnumerable jaegerLogs = null;
+
+ if (span?.Events?.Events != null)
+ {
+ jaegerLogs = span.Events.Events.Select(e => e.ToJaegerLog()).AsEnumerable();
+ }
+
+ IEnumerable refs = null;
+
+ if (span?.Links?.Links != null)
+ {
+ refs = span.Links.Links.Select(l => l.ToJaegerSpanRef()).Where(l => l != null).AsEnumerable();
+ }
+
+ var parentSpanId = new Int128(span.ParentSpanId);
+
+ var traceId = span?.Context?.TraceId == null ? Int128.Empty : new Int128(span.Context.TraceId);
+ var spanId = span?.Context?.SpanId == null ? Int128.Empty : new Int128(span.Context.SpanId);
+
+ return new JaegerSpan
+ {
+ TraceIdHigh = traceId.High,
+ TraceIdLow = traceId.Low,
+ SpanId = spanId.Low,
+ ParentSpanId = parentSpanId.Low,
+ OperationName = span.Name,
+ References = refs,
+ Flags = (span.Context.TraceOptions & ActivityTraceFlags.Recorded) > 0 ? 0x1 : 0,
+ StartTime = ToEpochMicroseconds(span.StartTimestamp),
+ Duration = ToEpochMicroseconds(span.EndTimestamp) - ToEpochMicroseconds(span.StartTimestamp),
+ JaegerTags = jaegerTags,
+ Logs = jaegerLogs,
+ };
+ }
+
+ public static JaegerTag ToJaegerTag(this KeyValuePair attribute)
+ {
+ switch (attribute.Value)
+ {
+ case string s:
+ return new JaegerTag { Key = attribute.Key, VType = JaegerTagType.STRING, VStr = s };
+ case int i:
+ return new JaegerTag { Key = attribute.Key, VType = JaegerTagType.LONG, VLong = Convert.ToInt64(i) };
+ case long l:
+ return new JaegerTag { Key = attribute.Key, VType = JaegerTagType.LONG, VLong = l };
+ case float f:
+ return new JaegerTag { Key = attribute.Key, VType = JaegerTagType.DOUBLE, VDouble = Convert.ToDouble(f) };
+ case double d:
+ return new JaegerTag { Key = attribute.Key, VType = JaegerTagType.DOUBLE, VDouble = d };
+ case bool b:
+ return new JaegerTag { Key = attribute.Key, VType = JaegerTagType.BOOL, VBool = b };
+ }
+
+ return new JaegerTag { Key = attribute.Key, VType = JaegerTagType.STRING, VStr = attribute.Value.ToString() };
+ }
+
+ public static JaegerLog ToJaegerLog(this ITimedEvent timedEvent)
+ {
+ var tags = timedEvent.Event.Attributes.Select(a => a.ToJaegerTag()).ToList();
+ tags.Add(new JaegerTag { Key = "description", VType = JaegerTagType.STRING, VStr = timedEvent.Event.Name });
+
+ return new JaegerLog
+ {
+ Timestamp = timedEvent.Timestamp.ToEpochMicroseconds(),
+ Fields = tags,
+ };
+ }
+
+ public static JaegerSpanRef ToJaegerSpanRef(this ILink link)
+ {
+ var traceId = link?.Context?.TraceId == null ? Int128.Empty : new Int128(link.Context.TraceId);
+ var spanId = link?.Context?.SpanId == null ? Int128.Empty : new Int128(link.Context.SpanId);
+
+ return new JaegerSpanRef
+ {
+ TraceIdHigh = traceId.High,
+ TraceIdLow = traceId.Low,
+ SpanId = spanId.Low,
+ RefType = JaegerSpanRefType.CHILD_OF,
+ };
+ }
+
+ public static long ToEpochMicroseconds(this DateTime timestamp)
+ {
+ // Truncate sub-microsecond precision before offsetting by the Unix Epoch to avoid
+ // the last digit being off by one for dates that result in negative Unix times
+ long microseconds = timestamp.Ticks / TicksPerMicrosecond;
+ return microseconds - UnixEpochMicroseconds;
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerExporterException.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerExporterException.cs
new file mode 100644
index 00000000000..c31906dbc27
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerExporterException.cs
@@ -0,0 +1,28 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Implementation
+{
+ using System;
+
+ public class JaegerExporterException : Exception
+ {
+ public JaegerExporterException(string message, Exception originalException)
+ : base(message, originalException)
+ {
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerLog.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerLog.cs
new file mode 100644
index 00000000000..0ddef52eac2
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerLog.cs
@@ -0,0 +1,101 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Implementation
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Thrift.Protocols;
+ using Thrift.Protocols.Entities;
+
+ public class JaegerLog : TAbstractBase
+ {
+ public JaegerLog()
+ {
+ }
+
+ public JaegerLog(long timestamp, IEnumerable fields)
+ : this()
+ {
+ this.Timestamp = timestamp;
+ this.Fields = fields ?? Enumerable.Empty();
+ }
+
+ public long Timestamp { get; set; }
+
+ public IEnumerable Fields { get; set; }
+
+ public async Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken)
+ {
+ oprot.IncrementRecursionDepth();
+ try
+ {
+ var struc = new TStruct("Log");
+ await oprot.WriteStructBeginAsync(struc, cancellationToken);
+
+ var field = new TField
+ {
+ Name = "timestamp",
+ Type = TType.I64,
+ ID = 1,
+ };
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteI64Async(this.Timestamp, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+
+ field.Name = "fields";
+ field.Type = TType.List;
+ field.ID = 2;
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ {
+ await oprot.WriteListBeginAsync(new TList(TType.Struct, this.Fields.Count()), cancellationToken);
+
+ foreach (JaegerTag jt in this.Fields)
+ {
+ await jt.WriteAsync(oprot, cancellationToken);
+ }
+
+ await oprot.WriteListEndAsync(cancellationToken);
+ }
+
+ await oprot.WriteFieldEndAsync(cancellationToken);
+ await oprot.WriteFieldStopAsync(cancellationToken);
+ await oprot.WriteStructEndAsync(cancellationToken);
+ }
+ finally
+ {
+ oprot.DecrementRecursionDepth();
+ }
+ }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder("Log(");
+ sb.Append(", Timestamp: ");
+ sb.Append(this.Timestamp);
+ sb.Append(", Fields: ");
+ sb.Append(this.Fields);
+ sb.Append(")");
+ return sb.ToString();
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerSpan.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerSpan.cs
new file mode 100644
index 00000000000..c11f1443f4d
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerSpan.cs
@@ -0,0 +1,255 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Implementation
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Thrift.Protocols;
+ using Thrift.Protocols.Entities;
+
+ public class JaegerSpan : TAbstractBase
+ {
+ public JaegerSpan()
+ {
+ }
+
+ public JaegerSpan(long traceIdLow, long traceIdHigh, long spanId, long parentSpanId, string operationName, int flags, long startTime, long duration)
+ : this()
+ {
+ this.TraceIdLow = traceIdLow;
+ this.TraceIdHigh = traceIdHigh;
+ this.SpanId = spanId;
+ this.ParentSpanId = parentSpanId;
+ this.OperationName = operationName;
+ this.Flags = flags;
+ this.StartTime = startTime;
+ this.Duration = duration;
+ }
+
+ public long TraceIdLow { get; set; }
+
+ public long TraceIdHigh { get; set; }
+
+ public long SpanId { get; set; }
+
+ public long ParentSpanId { get; set; }
+
+ public string OperationName { get; set; }
+
+ public IEnumerable References { get; set; }
+
+ public int Flags { get; set; }
+
+ public long StartTime { get; set; }
+
+ public long Duration { get; set; }
+
+ public IEnumerable JaegerTags { get; set; }
+
+ public IEnumerable Logs { get; set; }
+
+ public async Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken)
+ {
+ oprot.IncrementRecursionDepth();
+ try
+ {
+ var struc = new TStruct("Span");
+ await oprot.WriteStructBeginAsync(struc, cancellationToken);
+
+ var field = new TField
+ {
+ Name = "traceIdLow",
+ Type = TType.I64,
+ ID = 1,
+ };
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteI64Async(this.TraceIdLow, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+
+ field.Name = "traceIdHigh";
+ field.Type = TType.I64;
+ field.ID = 2;
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteI64Async(this.TraceIdHigh, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+
+ field.Name = "spanId";
+ field.Type = TType.I64;
+ field.ID = 3;
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteI64Async(this.SpanId, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+
+ field.Name = "parentSpanId";
+ field.Type = TType.I64;
+ field.ID = 4;
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteI64Async(this.ParentSpanId, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+
+ field.Name = "operationName";
+ field.Type = TType.String;
+ field.ID = 5;
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteStringAsync(this.OperationName, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+
+ if (this.References is IEnumerable references)
+ {
+ field.Name = "references";
+ field.Type = TType.List;
+ field.ID = 6;
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ {
+ await oprot.WriteListBeginAsync(new TList(TType.Struct, references.Count()), cancellationToken);
+
+ foreach (JaegerSpanRef sr in references)
+ {
+ await sr.WriteAsync(oprot, cancellationToken);
+ }
+
+ await oprot.WriteListEndAsync(cancellationToken);
+ }
+
+ await oprot.WriteFieldEndAsync(cancellationToken);
+ }
+
+ field.Name = "flags";
+ field.Type = TType.I32;
+ field.ID = 7;
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteI32Async(this.Flags, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+
+ field.Name = "startTime";
+ field.Type = TType.I64;
+ field.ID = 8;
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteI64Async(this.StartTime, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+
+ field.Name = "duration";
+ field.Type = TType.I64;
+ field.ID = 9;
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteI64Async(this.Duration, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+
+ if (this.JaegerTags is IEnumerable jaegerTags)
+ {
+ field.Name = "JaegerTags";
+ field.Type = TType.List;
+ field.ID = 10;
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ {
+ await oprot.WriteListBeginAsync(new TList(TType.Struct, jaegerTags.Count()), cancellationToken);
+
+ foreach (JaegerTag jt in jaegerTags)
+ {
+ await jt.WriteAsync(oprot, cancellationToken);
+ }
+
+ await oprot.WriteListEndAsync(cancellationToken);
+ }
+
+ await oprot.WriteFieldEndAsync(cancellationToken);
+ }
+
+ if (this.Logs is IEnumerable logs)
+ {
+ field.Name = "logs";
+ field.Type = TType.List;
+ field.ID = 11;
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ {
+ await oprot.WriteListBeginAsync(new TList(TType.Struct, logs.Count()), cancellationToken);
+
+ foreach (JaegerLog jl in logs)
+ {
+ await jl.WriteAsync(oprot, cancellationToken);
+ }
+
+ await oprot.WriteListEndAsync(cancellationToken);
+ }
+
+ await oprot.WriteFieldEndAsync(cancellationToken);
+ }
+
+ await oprot.WriteFieldStopAsync(cancellationToken);
+ await oprot.WriteStructEndAsync(cancellationToken);
+ }
+ finally
+ {
+ oprot.DecrementRecursionDepth();
+ }
+ }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder("Span(");
+ sb.Append(", TraceIdLow: ");
+ sb.Append(this.TraceIdLow);
+ sb.Append(", TraceIdHigh: ");
+ sb.Append(this.TraceIdHigh);
+ sb.Append(", SpanId: ");
+ sb.Append(this.SpanId);
+ sb.Append(", ParentSpanId: ");
+ sb.Append(this.ParentSpanId);
+ sb.Append(", OperationName: ");
+ sb.Append(this.OperationName);
+ if (this.References is IEnumerable jaegerSpanRef)
+ {
+ sb.Append(", References: ");
+ sb.Append(jaegerSpanRef);
+ }
+
+ sb.Append(", Flags: ");
+ sb.Append(this.Flags);
+ sb.Append(", StartTime: ");
+ sb.Append(this.StartTime);
+ sb.Append(", Duration: ");
+ sb.Append(this.Duration);
+ if (this.JaegerTags is IEnumerable tags)
+ {
+ sb.Append(", JaegerTags: ");
+ sb.Append(tags);
+ }
+
+ if (this.Logs is IEnumerable logs)
+ {
+ sb.Append(", Logs: ");
+ sb.Append(logs);
+ }
+
+ sb.Append(")");
+ return sb.ToString();
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerSpanRef.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerSpanRef.cs
new file mode 100644
index 00000000000..d9824380630
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerSpanRef.cs
@@ -0,0 +1,119 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Implementation
+{
+ using System;
+ using System.Text;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Thrift.Protocols;
+ using Thrift.Protocols.Entities;
+
+ public class JaegerSpanRef : TAbstractBase
+ {
+ public JaegerSpanRef()
+ {
+ }
+
+ public JaegerSpanRef(JaegerSpanRefType refType, long traceIdLow, long traceIdHigh, long spanId)
+ : this()
+ {
+ this.RefType = refType;
+ this.TraceIdLow = traceIdLow;
+ this.TraceIdHigh = traceIdHigh;
+ this.SpanId = spanId;
+ }
+
+ public JaegerSpanRefType RefType { get; set; }
+
+ public long TraceIdLow { get; set; }
+
+ public long TraceIdHigh { get; set; }
+
+ public long SpanId { get; set; }
+
+ public async Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken)
+ {
+ oprot.IncrementRecursionDepth();
+ try
+ {
+ var struc = new TStruct("SpanRef");
+ await oprot.WriteStructBeginAsync(struc, cancellationToken);
+
+ var field = new TField
+ {
+ Name = "refType",
+ Type = TType.I32,
+ ID = 1,
+ };
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteI32Async((int)this.RefType, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+
+ field.Name = "traceIdLow";
+ field.Type = TType.I64;
+ field.ID = 2;
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteI64Async(this.TraceIdLow, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+
+ field.Name = "traceIdHigh";
+ field.Type = TType.I64;
+ field.ID = 3;
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteI64Async(this.TraceIdHigh, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+
+ field.Name = "spanId";
+ field.Type = TType.I64;
+ field.ID = 4;
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteI64Async(this.SpanId, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+ await oprot.WriteFieldStopAsync(cancellationToken);
+ await oprot.WriteStructEndAsync(cancellationToken);
+ }
+ finally
+ {
+ oprot.DecrementRecursionDepth();
+ }
+ }
+
+ ///
+ ///
+ ///
+ /// A string representation of the object.
+ public override string ToString()
+ {
+ var sb = new StringBuilder("SpanRef(");
+ sb.Append(", RefType: ");
+ sb.Append(this.RefType);
+ sb.Append(", TraceIdLow: ");
+ sb.Append(this.TraceIdLow);
+ sb.Append(", TraceIdHigh: ");
+ sb.Append(this.TraceIdHigh);
+ sb.Append(", SpanId: ");
+ sb.Append(this.SpanId);
+ sb.Append(")");
+ return sb.ToString();
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerSpanRefType.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerSpanRefType.cs
new file mode 100644
index 00000000000..4b49dfef5de
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerSpanRefType.cs
@@ -0,0 +1,36 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Implementation
+{
+ using System;
+
+ ///
+ /// Represents the different types of Jaeger Spans.
+ ///
+ public enum JaegerSpanRefType
+ {
+ ///
+ /// A child span
+ ///
+ CHILD_OF = 0,
+
+ ///
+ /// A sibling span
+ ///
+ FOLLOWS_FROM = 1,
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerTag.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerTag.cs
new file mode 100644
index 00000000000..71853ebe3ef
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerTag.cs
@@ -0,0 +1,180 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Implementation
+{
+ using System;
+ using System.Text;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Thrift.Protocols;
+ using Thrift.Protocols.Entities;
+
+ public class JaegerTag : TAbstractBase
+ {
+ public JaegerTag()
+ {
+ }
+
+ public JaegerTag(string key, JaegerTagType vType)
+ : this()
+ {
+ this.Key = key;
+ this.VType = vType;
+ }
+
+ public string Key { get; set; }
+
+ public JaegerTagType VType { get; set; }
+
+ public string VStr { get; set; }
+
+ public double? VDouble { get; set; }
+
+ public bool? VBool { get; set; }
+
+ public long? VLong { get; set; }
+
+ public byte[] VBinary { get; set; }
+
+ public async Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken)
+ {
+ oprot.IncrementRecursionDepth();
+ try
+ {
+ var struc = new TStruct("Tag");
+ await oprot.WriteStructBeginAsync(struc, cancellationToken);
+
+ var field = new TField
+ {
+ Name = "key",
+ Type = TType.String,
+ ID = 1,
+ };
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteStringAsync(this.Key, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+
+ field.Name = "vType";
+ field.Type = TType.I32;
+ field.ID = 2;
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteI32Async((int)this.VType, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+
+ if (this.VStr != null)
+ {
+ field.Name = "vStr";
+ field.Type = TType.String;
+ field.ID = 3;
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteStringAsync(this.VStr, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+ }
+
+ if (this.VDouble.HasValue)
+ {
+ field.Name = "vDouble";
+ field.Type = TType.Double;
+ field.ID = 4;
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteDoubleAsync(this.VDouble.Value, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+ }
+
+ if (this.VBool.HasValue)
+ {
+ field.Name = "vBool";
+ field.Type = TType.Bool;
+ field.ID = 5;
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteBoolAsync(this.VBool.Value, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+ }
+
+ if (this.VLong.HasValue)
+ {
+ field.Name = "vLong";
+ field.Type = TType.I64;
+ field.ID = 6;
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteI64Async(this.VLong.Value, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+ }
+
+ if (this.VBinary != null)
+ {
+ field.Name = "vBinary";
+ field.Type = TType.String;
+ field.ID = 7;
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteBinaryAsync(this.VBinary, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+ }
+
+ await oprot.WriteFieldStopAsync(cancellationToken);
+ await oprot.WriteStructEndAsync(cancellationToken);
+ }
+ finally
+ {
+ oprot.DecrementRecursionDepth();
+ }
+ }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder("Tag(");
+ sb.Append(", Key: ");
+ sb.Append(this.Key);
+ sb.Append(", VType: ");
+ sb.Append(this.VType);
+ if (this.VStr != null)
+ {
+ sb.Append(", VStr: ");
+ sb.Append(this.VStr);
+ }
+
+ if (this.VDouble.HasValue)
+ {
+ sb.Append(", VDouble: ");
+ sb.Append(this.VDouble);
+ }
+
+ if (this.VBool.HasValue)
+ {
+ sb.Append(", VBool: ");
+ sb.Append(this.VBool);
+ }
+
+ if (this.VLong.HasValue)
+ {
+ sb.Append(", VLong: ");
+ sb.Append(this.VLong);
+ }
+
+ if (this.VBinary != null)
+ {
+ sb.Append(", VBinary: ");
+ sb.Append(this.VBinary);
+ }
+
+ sb.Append(")");
+ return sb.ToString();
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerTagType.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerTagType.cs
new file mode 100644
index 00000000000..868a41cc461
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerTagType.cs
@@ -0,0 +1,51 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Implementation
+{
+ using System;
+
+ ///
+ /// Indicates the data type of a Jaeger tag.
+ ///
+ public enum JaegerTagType
+ {
+ ///
+ /// Tag contains a string
+ ///
+ STRING = 0,
+
+ ///
+ /// Tag contains a double
+ ///
+ DOUBLE = 1,
+
+ ///
+ /// Tag contains a boolean
+ ///
+ BOOL = 2,
+
+ ///
+ /// Tag contains a long
+ ///
+ LONG = 3,
+
+ ///
+ /// Tag contains binary data
+ ///
+ BINARY = 4,
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerThriftClient.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerThriftClient.cs
new file mode 100644
index 00000000000..061b5368232
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerThriftClient.cs
@@ -0,0 +1,50 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Implementation
+{
+ using System;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Thrift;
+ using Thrift.Protocols;
+ using Thrift.Protocols.Entities;
+
+ public class JaegerThriftClient : TBaseClient, IDisposable
+ {
+ public JaegerThriftClient(TProtocol protocol)
+ : this(protocol, protocol)
+ {
+ }
+
+ public JaegerThriftClient(TProtocol inputProtocol, TProtocol outputProtocol)
+ : base(inputProtocol, outputProtocol)
+ {
+ }
+
+ public async Task EmitBatchAsync(Batch batch, CancellationToken cancellationToken)
+ {
+ await this.OutputProtocol.WriteMessageBeginAsync(new TMessage("emitBatch", TMessageType.Oneway, this.SeqId), cancellationToken);
+
+ var args = new EmitBatchArgs();
+ args.Batch = batch;
+
+ await args.WriteAsync(this.OutputProtocol, cancellationToken);
+ await this.OutputProtocol.WriteMessageEndAsync(cancellationToken);
+ await this.OutputProtocol.Transport.FlushAsync(cancellationToken);
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerThriftClientTransport.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerThriftClientTransport.cs
new file mode 100644
index 00000000000..4e97b346021
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerThriftClientTransport.cs
@@ -0,0 +1,113 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Implementation
+{
+ using System;
+ using System.IO;
+ using System.Net.Sockets;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Thrift.Transports;
+
+ public class JaegerThriftClientTransport : TClientTransport
+ {
+ private readonly IJaegerUdpClient udpClient;
+ private readonly MemoryStream byteStream;
+ private bool isDisposed = false;
+
+ public JaegerThriftClientTransport(string host, int port)
+ : this(host, port, new MemoryStream(), new JaegerUdpClient())
+ {
+ }
+
+ public JaegerThriftClientTransport(string host, int port, MemoryStream stream, IJaegerUdpClient client)
+ {
+ this.byteStream = stream;
+ this.udpClient = client;
+ this.udpClient.Connect(host, port);
+ }
+
+ public override bool IsOpen => this.udpClient.Connected;
+
+ public override void Close()
+ {
+ this.udpClient.Close();
+ }
+
+ public override Task FlushAsync(CancellationToken cancellationToken)
+ {
+ var bytes = this.byteStream.ToArray();
+
+ if (bytes.Length == 0)
+ {
+ return Task.CompletedTask;
+ }
+
+ this.byteStream.SetLength(0);
+
+ try
+ {
+ return this.udpClient.SendAsync(bytes, bytes.Length);
+ }
+ catch (SocketException se)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.Unknown, $"Cannot flush because of socket exception. UDP Packet size was {bytes.Length}. Exception message: {se.Message}");
+ }
+ catch (Exception e)
+ {
+ throw new TTransportException(TTransportException.ExceptionType.Unknown, $"Cannot flush closed transport. {e.Message}");
+ }
+ }
+
+ public override Task OpenAsync(CancellationToken cancellationToken)
+ {
+ // Do nothing
+ return Task.CompletedTask;
+ }
+
+ public override Task ReadAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override Task WriteAsync(byte[] buffer, CancellationToken cancellationToken)
+ {
+ return this.WriteAsync(buffer, 0, buffer.Length, cancellationToken);
+ }
+
+ public override Task WriteAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken)
+ {
+ return this.byteStream.WriteAsync(buffer, offset, length, cancellationToken);
+ }
+
+ public override string ToString()
+ {
+ return $"{nameof(JaegerThriftClientTransport)}(Client={this.udpClient.RemoteEndPoint})";
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (!this.isDisposed && disposing)
+ {
+ this.byteStream?.Dispose();
+ this.udpClient?.Dispose();
+ }
+
+ this.isDisposed = true;
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerTraceExporterHandler.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerTraceExporterHandler.cs
new file mode 100644
index 00000000000..5f1e64739c0
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerTraceExporterHandler.cs
@@ -0,0 +1,75 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Implementation
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Net.Sockets;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using OpenTelemetry.Trace;
+ using OpenTelemetry.Trace.Export;
+ using Thrift.Protocols;
+
+ public class JaegerTraceExporterHandler : IHandler, IDisposable
+ {
+ private readonly IJaegerUdpBatcher jaegerAgentUdpBatcher;
+ private bool disposedValue = false; // To detect redundant dispose calls
+
+ public JaegerTraceExporterHandler(JaegerExporterOptions options)
+ : this(new JaegerUdpBatcher(options))
+ {
+ }
+
+ public JaegerTraceExporterHandler(IJaegerUdpBatcher jaegerAgentUdpBatcher)
+ {
+ this.jaegerAgentUdpBatcher = jaegerAgentUdpBatcher;
+ }
+
+ public async Task ExportAsync(IEnumerable spanDataList)
+ {
+ var jaegerspans = spanDataList.Select(sdl => sdl.ToJaegerSpan());
+
+ foreach (var s in jaegerspans)
+ {
+ await this.jaegerAgentUdpBatcher.AppendAsync(s, CancellationToken.None);
+ }
+
+ await this.jaegerAgentUdpBatcher.FlushAsync(CancellationToken.None);
+ }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing).
+ this.Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!this.disposedValue)
+ {
+ if (disposing)
+ {
+ this.jaegerAgentUdpBatcher.Dispose();
+ }
+
+ this.disposedValue = true;
+ }
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerUdpBatcher.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerUdpBatcher.cs
new file mode 100644
index 00000000000..8827909fb10
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerUdpBatcher.cs
@@ -0,0 +1,156 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Implementation
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Thrift.Protocols;
+
+ public class JaegerUdpBatcher : IJaegerUdpBatcher
+ {
+ private const int DefaultMaxPacketSize = 65000;
+ private readonly int? maxPacketSize;
+ private readonly ITProtocolFactory protocolFactory;
+ private readonly JaegerThriftClientTransport clientTransport;
+ private readonly JaegerThriftClient thriftClient;
+ private readonly Process process;
+ private readonly int processByteSize;
+ private readonly List currentBatch = new List();
+
+ private int batchByteSize;
+
+ private bool disposedValue = false; // To detect redundant calls
+
+ public JaegerUdpBatcher(JaegerExporterOptions options)
+ {
+ this.maxPacketSize = options.MaxPacketSize == 0 ? DefaultMaxPacketSize : options.MaxPacketSize;
+ this.protocolFactory = new TCompactProtocol.Factory();
+ this.clientTransport = new JaegerThriftClientTransport(options.AgentHost, options.AgentPort.Value);
+ this.thriftClient = new JaegerThriftClient(this.protocolFactory.GetProtocol(this.clientTransport));
+ this.process = new Process(options.ServiceName, options.ProcessTags);
+ this.processByteSize = this.GetSize(this.process);
+ this.batchByteSize = this.processByteSize;
+ }
+
+ public async Task AppendAsync(JaegerSpan span, CancellationToken cancellationToken)
+ {
+ int spanSize = this.GetSize(span);
+
+ if (spanSize > this.maxPacketSize)
+ {
+ throw new JaegerExporterException($"ThriftSender received a span that was too large, size = {spanSize}, max = {this.maxPacketSize}", null);
+ }
+
+ this.batchByteSize += spanSize;
+ if (this.batchByteSize <= this.maxPacketSize)
+ {
+ this.currentBatch.Add(span);
+
+ if (this.batchByteSize < this.maxPacketSize)
+ {
+ return 0;
+ }
+
+ return await this.FlushAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ int n;
+
+ try
+ {
+ n = await this.FlushAsync(cancellationToken).ConfigureAwait(false);
+ }
+ catch (JaegerExporterException ex)
+ {
+ // +1 for the span not submitted in the buffer above
+ throw new JaegerExporterException(ex.Message, ex);
+ }
+
+ this.currentBatch.Add(span);
+ this.batchByteSize = this.processByteSize + spanSize;
+ return n;
+ }
+
+ public async Task FlushAsync(CancellationToken cancellationToken)
+ {
+ int n = this.currentBatch.Count;
+
+ if (n == 0)
+ {
+ return 0;
+ }
+
+ try
+ {
+ await this.SendAsync(this.process, this.currentBatch, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ this.currentBatch.Clear();
+ this.batchByteSize = this.processByteSize;
+ }
+
+ return n;
+ }
+
+ public virtual Task CloseAsync(CancellationToken cancellationToken) => this.FlushAsync(cancellationToken);
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing).
+ this.Dispose(true);
+ }
+
+ protected async Task SendAsync(Process process, List spans, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var batch = new Batch(process, spans);
+ await this.thriftClient.EmitBatchAsync(batch, cancellationToken).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ throw new JaegerExporterException($"Could not send {spans.Count} spans", ex);
+ }
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!this.disposedValue)
+ {
+ if (disposing)
+ {
+ this.thriftClient.Dispose();
+ this.clientTransport.Dispose();
+ }
+
+ this.disposedValue = true;
+ }
+ }
+
+ private int GetSize(TAbstractBase thriftBase)
+ {
+ using (var memoryTransport = new InMemoryTransport())
+ {
+ thriftBase.WriteAsync(this.protocolFactory.GetProtocol(memoryTransport), CancellationToken.None).GetAwaiter().GetResult();
+ return memoryTransport.GetBuffer().Length;
+ }
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerUdpClient.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerUdpClient.cs
new file mode 100644
index 00000000000..146d7f32a98
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerUdpClient.cs
@@ -0,0 +1,45 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Implementation
+{
+ using System;
+ using System.Net;
+ using System.Net.Sockets;
+ using System.Threading.Tasks;
+
+ public class JaegerUdpClient : IJaegerUdpClient
+ {
+ private readonly UdpClient client;
+
+ public JaegerUdpClient()
+ {
+ this.client = new UdpClient();
+ }
+
+ public bool Connected => this.client.Client.Connected;
+
+ public EndPoint RemoteEndPoint => this.client.Client.RemoteEndPoint;
+
+ public void Close() => this.client.Close();
+
+ public void Connect(string host, int port) => this.client.Connect(host, port);
+
+ public Task SendAsync(byte[] datagram, int bytes) => this.client.SendAsync(datagram, bytes);
+
+ public void Dispose() => this.client.Dispose();
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/Process.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/Process.cs
new file mode 100644
index 00000000000..b08c84d83ca
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/Process.cs
@@ -0,0 +1,116 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Implementation
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Thrift.Protocols;
+ using Thrift.Protocols.Entities;
+
+ public class Process : TAbstractBase
+ {
+ public Process()
+ {
+ }
+
+ public Process(string serviceName, IDictionary processTags)
+ : this()
+ {
+ this.ServiceName = serviceName;
+
+ if (processTags != null)
+ {
+ this.Tags = new List();
+ this.Tags.AddRange(processTags.Select(pt => pt.ToJaegerTag()));
+ }
+ }
+
+ public string ServiceName { get; set; }
+
+ public List Tags { get; set; }
+
+ public async Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken)
+ {
+ oprot.IncrementRecursionDepth();
+
+ try
+ {
+ var struc = new TStruct("Process");
+ await oprot.WriteStructBeginAsync(struc, cancellationToken);
+
+ var field = new TField
+ {
+ Name = "serviceName",
+ Type = TType.String,
+ ID = 1,
+ };
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ await oprot.WriteStringAsync(this.ServiceName, cancellationToken);
+ await oprot.WriteFieldEndAsync(cancellationToken);
+
+ if (this.Tags != null)
+ {
+ field.Name = "tags";
+ field.Type = TType.List;
+ field.ID = 2;
+
+ await oprot.WriteFieldBeginAsync(field, cancellationToken);
+ {
+ await oprot.WriteListBeginAsync(new TList(TType.Struct, this.Tags.Count), cancellationToken);
+
+ foreach (JaegerTag jt in this.Tags)
+ {
+ await jt.WriteAsync(oprot, cancellationToken);
+ }
+
+ await oprot.WriteListEndAsync(cancellationToken);
+ }
+
+ await oprot.WriteFieldEndAsync(cancellationToken);
+ }
+
+ await oprot.WriteFieldStopAsync(cancellationToken);
+ await oprot.WriteStructEndAsync(cancellationToken);
+ }
+ finally
+ {
+ oprot.DecrementRecursionDepth();
+ }
+ }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder("Process(");
+ sb.Append(", ServiceName: ");
+ sb.Append(this.ServiceName);
+
+ if (this.Tags != null)
+ {
+ sb.Append(", Tags: ");
+ sb.Append(this.Tags);
+ }
+
+ sb.Append(")");
+ return sb.ToString();
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/JaegerExporter.cs b/src/OpenTelemetry.Exporter.Jaeger/JaegerExporter.cs
new file mode 100644
index 00000000000..60f200f26a4
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/JaegerExporter.cs
@@ -0,0 +1,128 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger
+{
+ using System;
+ using OpenTelemetry.Exporter.Jaeger.Implementation;
+ using OpenTelemetry.Trace.Export;
+
+ public class JaegerExporter : IDisposable
+ {
+ public const string DefaultAgentUdpHost = "localhost";
+ public const int DefaultAgentUdpCompactPort = 6831;
+ public const int DefaultMaxPacketSize = 65000;
+
+ private const string ExporterName = "JaegerTraceExporter";
+
+ private readonly object @lock = new object();
+ private readonly JaegerExporterOptions options;
+ private readonly IExportComponent exportComponent;
+
+ private volatile bool isInitialized = false;
+ private JaegerTraceExporterHandler handler;
+ private bool disposedValue = false; // To detect redundant dispose calls
+
+ public JaegerExporter(JaegerExporterOptions options, IExportComponent exportComponent)
+ {
+ this.ValidateOptions(options);
+ this.InitializeOptions(options);
+
+ this.options = options;
+ this.exportComponent = exportComponent;
+ }
+
+ public void Start()
+ {
+ lock (this.@lock)
+ {
+ if (this.isInitialized)
+ {
+ return;
+ }
+
+ if (this.exportComponent != null)
+ {
+ this.handler = new JaegerTraceExporterHandler(this.options);
+ this.exportComponent.SpanExporter.RegisterHandler(ExporterName, this.handler);
+ }
+ }
+ }
+
+ public void Stop()
+ {
+ if (!this.isInitialized)
+ {
+ return;
+ }
+
+ lock (this.@lock)
+ {
+ if (this.exportComponent != null)
+ {
+ this.exportComponent.SpanExporter.UnregisterHandler(ExporterName);
+ }
+ }
+
+ this.isInitialized = false;
+ }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing).
+ this.Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!this.disposedValue)
+ {
+ if (disposing)
+ {
+ this.handler.Dispose();
+ }
+
+ this.disposedValue = true;
+ }
+ }
+
+ private void ValidateOptions(JaegerExporterOptions options)
+ {
+ if (string.IsNullOrWhiteSpace(options.ServiceName))
+ {
+ throw new ArgumentException("ServiceName", "Service Name is required.");
+ }
+ }
+
+ private void InitializeOptions(JaegerExporterOptions options)
+ {
+ if (string.IsNullOrWhiteSpace(options.AgentHost))
+ {
+ options.AgentHost = DefaultAgentUdpHost;
+ }
+
+ if (!options.AgentPort.HasValue)
+ {
+ options.AgentPort = DefaultAgentUdpCompactPort;
+ }
+
+ if (!options.MaxPacketSize.HasValue)
+ {
+ options.MaxPacketSize = DefaultMaxPacketSize;
+ }
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/JaegerExporterOptions.cs b/src/OpenTelemetry.Exporter.Jaeger/JaegerExporterOptions.cs
new file mode 100644
index 00000000000..fef770a198f
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/JaegerExporterOptions.cs
@@ -0,0 +1,34 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger
+{
+ using System;
+ using System.Collections.Generic;
+
+ public class JaegerExporterOptions
+ {
+ public string ServiceName { get; set; }
+
+ public string AgentHost { get; set; }
+
+ public int? AgentPort { get; set; }
+
+ public int? MaxPacketSize { get; set; }
+
+ public Dictionary ProcessTags { get; set; }
+ }
+}
diff --git a/src/OpenTelemetry.Exporter.Jaeger/OpenTelemetry.Exporter.Jaeger.csproj b/src/OpenTelemetry.Exporter.Jaeger/OpenTelemetry.Exporter.Jaeger.csproj
new file mode 100644
index 00000000000..879d3410513
--- /dev/null
+++ b/src/OpenTelemetry.Exporter.Jaeger/OpenTelemetry.Exporter.Jaeger.csproj
@@ -0,0 +1,16 @@
+
+
+
+ net46;netstandard2.0
+ netstandard2.0
+ OpenTelemetry authors
+ https://opentelemetry.io/img/logos/opentelemetry-icon-color.png
+ https://OpenTelemetry.io
+ Tracing;OpenTelemetry;Management;Monitoring;Jaeger;distributed-tracing
+
+
+
+
+
+
+
diff --git a/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/JaegerSpanConverterTest.cs b/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/JaegerSpanConverterTest.cs
new file mode 100644
index 00000000000..7c1ecb959b5
--- /dev/null
+++ b/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/JaegerSpanConverterTest.cs
@@ -0,0 +1,539 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Tests.Implementation
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Linq;
+ using OpenTelemetry.Exporter.Jaeger.Implementation;
+ using OpenTelemetry.Resources;
+ using OpenTelemetry.Trace;
+ using OpenTelemetry.Trace.Export;
+ using Xunit;
+
+ public class JaegerSpanConverterTest
+ {
+ private const long MillisPerSecond = 1000L;
+ private const long NanosPerMillisecond = 1000 * 1000;
+ private const long NanosPerSecond = NanosPerMillisecond * MillisPerSecond;
+
+ public JaegerSpanConverterTest()
+ {
+ }
+
+ [Fact]
+ public void JaegerSpanConverterTest_ConvertSpanToJaegerSpan_AllPropertiesSet()
+ {
+ var startTimestamp = DateTime.Now;
+ var endTimestamp = startTimestamp.AddSeconds(60);
+ var eventTimestamp = DateTime.Now;
+
+ var traceId = ActivityTraceId.CreateRandom();
+ var traceIdAsInt = new Int128(traceId);
+ var spanId = ActivitySpanId.CreateRandom();
+ var spanIdAsInt = new Int128(spanId);
+ var parentSpanId = ActivitySpanId.CreateRandom();
+ var attributes = Attributes.Create(new Dictionary{
+ { "stringKey", "value"},
+ { "longKey", 1L},
+ { "longKey2", 1 },
+ { "doubleKey", 1D},
+ { "doubleKey2", 1F},
+ { "boolKey", true},
+ }, 0);
+ var events = TimedEvents.Create(new List>
+ {
+ TimedEvent.Create(
+ eventTimestamp,
+ Event.Create(
+ "Event1",
+ new Dictionary
+ {
+ { "key", "value" },
+ }
+ )
+ ),
+ TimedEvent.Create(
+ eventTimestamp,
+ Event.Create(
+ "Event2",
+ new Dictionary
+ {
+ { "key", "value" },
+ }
+ )
+ ),
+ }, 0);
+
+ var linkedSpanId = ActivitySpanId.CreateRandom();
+
+ var link = Link.FromSpanContext(SpanContext.Create(
+ traceId,
+ linkedSpanId,
+ ActivityTraceFlags.Recorded,
+ Tracestate.Empty));
+
+ var linkTraceIdAsInt = new Int128(link.Context.TraceId);
+ var linkSpanIdAsInt = new Int128(link.Context.SpanId);
+
+ var links = LinkList.Create(new List { link }, 0);
+
+ var spanData = SpanData.Create(
+ SpanContext.Create(
+ traceId,
+ spanId,
+ ActivityTraceFlags.Recorded,
+ Tracestate.Empty
+ ),
+ parentSpanId,
+ Resource.Empty,
+ "Name",
+ startTimestamp,
+ attributes,
+ events,
+ links,
+ null,
+ Status.Ok,
+ SpanKind.Client,
+ endTimestamp
+ );
+
+ var jaegerSpan = spanData.ToJaegerSpan();
+
+ Assert.Equal("Name", jaegerSpan.OperationName);
+ Assert.Equal(2, jaegerSpan.Logs.Count());
+
+ Assert.Equal(traceIdAsInt.High, jaegerSpan.TraceIdHigh);
+ Assert.Equal(traceIdAsInt.Low, jaegerSpan.TraceIdLow);
+ Assert.Equal(spanIdAsInt.Low, jaegerSpan.SpanId);
+ Assert.Equal(new Int128(parentSpanId).Low, jaegerSpan.ParentSpanId);
+
+ Assert.Equal(links.Links.Count(), jaegerSpan.References.Count());
+ var references = jaegerSpan.References.ToArray();
+ var jaegerRef = references[0];
+ Assert.Equal(linkTraceIdAsInt.High, jaegerRef.TraceIdHigh);
+ Assert.Equal(linkTraceIdAsInt.Low, jaegerRef.TraceIdLow);
+ Assert.Equal(linkSpanIdAsInt.Low, jaegerRef.SpanId);
+
+ Assert.Equal(0x1, jaegerSpan.Flags);
+
+ Assert.Equal(startTimestamp.ToEpochMicroseconds(), jaegerSpan.StartTime);
+ Assert.Equal(endTimestamp.ToEpochMicroseconds() - startTimestamp.ToEpochMicroseconds(), jaegerSpan.Duration);
+
+ var tags = jaegerSpan.JaegerTags.ToArray();
+ var tag = tags[0];
+ Assert.Equal(JaegerTagType.STRING, tag.VType);
+ Assert.Equal("stringKey", tag.Key);
+ Assert.Equal("value", tag.VStr);
+ tag = tags[1];
+ Assert.Equal(JaegerTagType.LONG, tag.VType);
+ Assert.Equal("longKey", tag.Key);
+ Assert.Equal(1, tag.VLong);
+ tag = tags[2];
+ Assert.Equal(JaegerTagType.LONG, tag.VType);
+ Assert.Equal("longKey2", tag.Key);
+ Assert.Equal(1, tag.VLong);
+ tag = tags[3];
+ Assert.Equal(JaegerTagType.DOUBLE, tag.VType);
+ Assert.Equal("doubleKey", tag.Key);
+ Assert.Equal(1, tag.VDouble);
+ tag = tags[4];
+ Assert.Equal(JaegerTagType.DOUBLE, tag.VType);
+ Assert.Equal("doubleKey2", tag.Key);
+ Assert.Equal(1, tag.VDouble);
+ tag = tags[5];
+ Assert.Equal(JaegerTagType.BOOL, tag.VType);
+ Assert.Equal("boolKey", tag.Key);
+ Assert.Equal(true, tag.VBool);
+
+ var logs = jaegerSpan.Logs.ToArray();
+ var jaegerLog = logs[0];
+ Assert.Equal(events.Events.First().Timestamp.ToEpochMicroseconds(), jaegerLog.Timestamp);
+ Assert.Equal(jaegerLog.Fields.Count(), 2);
+ var eventFields = jaegerLog.Fields.ToArray();
+ var eventField = eventFields[0];
+ Assert.Equal("key", eventField.Key);
+ Assert.Equal("value", eventField.VStr);
+ eventField = eventFields[1];
+ Assert.Equal("description", eventField.Key);
+ Assert.Equal("Event1", eventField.VStr);
+
+ jaegerLog = logs[1];
+ Assert.Equal(events.Events.First().Timestamp.ToEpochMicroseconds(), jaegerLog.Timestamp);
+ Assert.Equal(jaegerLog.Fields.Count(), 2);
+ eventFields = jaegerLog.Fields.ToArray();
+ eventField = eventFields[0];
+ Assert.Equal("key", eventField.Key);
+ Assert.Equal("value", eventField.VStr);
+ eventField = eventFields[1];
+ Assert.Equal("description", eventField.Key);
+ Assert.Equal("Event2", eventField.VStr);
+ }
+
+ [Fact]
+ public void JaegerSpanConverterTest_ConvertSpanToJaegerSpan_NoAttributes()
+ {
+ var startTimestamp = DateTime.Now;
+ var endTimestamp = startTimestamp.AddSeconds(60);
+ var eventTimestamp = DateTime.Now;
+
+ var traceId = ActivityTraceId.CreateRandom();
+ var traceIdAsInt = new Int128(traceId);
+ var spanId = ActivitySpanId.CreateRandom();
+ var spanIdAsInt = new Int128(spanId);
+ var parentSpanId = ActivitySpanId.CreateRandom();
+ var events = TimedEvents.Create(new List>
+ {
+ TimedEvent.Create(
+ eventTimestamp,
+ Event.Create(
+ "Event1",
+ new Dictionary
+ {
+ { "key", "value" },
+ }
+ )
+ ),
+ TimedEvent.Create(
+ eventTimestamp,
+ Event.Create(
+ "Event2",
+ new Dictionary
+ {
+ { "key", "value" },
+ }
+ )
+ ),
+ }, 0);
+
+ var linkedSpanId = ActivitySpanId.CreateRandom();
+
+ var link = Link.FromSpanContext(SpanContext.Create(
+ traceId,
+ linkedSpanId,
+ ActivityTraceFlags.Recorded,
+ Tracestate.Empty));
+
+ var linkTraceIdAsInt = new Int128(link.Context.TraceId);
+ var linkSpanIdAsInt = new Int128(link.Context.SpanId);
+
+ var links = LinkList.Create(new List { link }, 0);
+
+ var spanData = SpanData.Create(
+ SpanContext.Create(
+ traceId,
+ spanId,
+ ActivityTraceFlags.Recorded,
+ Tracestate.Empty
+ ),
+ parentSpanId,
+ Resource.Empty,
+ "Name",
+ startTimestamp,
+ null,
+ events,
+ links,
+ null,
+ Status.Ok,
+ SpanKind.Client,
+ endTimestamp
+ );
+
+ var jaegerSpan = spanData.ToJaegerSpan();
+
+ Assert.Equal("Name", jaegerSpan.OperationName);
+ Assert.Equal(2, jaegerSpan.Logs.Count());
+
+ Assert.Equal(traceIdAsInt.High, jaegerSpan.TraceIdHigh);
+ Assert.Equal(traceIdAsInt.Low, jaegerSpan.TraceIdLow);
+ Assert.Equal(spanIdAsInt.Low, jaegerSpan.SpanId);
+ Assert.Equal(new Int128(parentSpanId).Low, jaegerSpan.ParentSpanId);
+
+ Assert.Equal(links.Links.Count(), jaegerSpan.References.Count());
+ var references = jaegerSpan.References.ToArray();
+ var jaegerRef = references[0];
+ Assert.Equal(linkTraceIdAsInt.High, jaegerRef.TraceIdHigh);
+ Assert.Equal(linkTraceIdAsInt.Low, jaegerRef.TraceIdLow);
+ Assert.Equal(linkSpanIdAsInt.Low, jaegerRef.SpanId);
+
+ Assert.Equal(0x1, jaegerSpan.Flags);
+
+ Assert.Equal(startTimestamp.ToEpochMicroseconds(), jaegerSpan.StartTime);
+ Assert.Equal(endTimestamp.ToEpochMicroseconds() - startTimestamp.ToEpochMicroseconds(), jaegerSpan.Duration);
+
+ Assert.Empty(jaegerSpan.JaegerTags);
+
+ var logs = jaegerSpan.Logs.ToArray();
+ var jaegerLog = logs[0];
+ Assert.Equal(events.Events.First().Timestamp.ToEpochMicroseconds(), jaegerLog.Timestamp);
+ Assert.Equal(jaegerLog.Fields.Count(), 2);
+ var eventFields = jaegerLog.Fields.ToArray();
+ var eventField = eventFields[0];
+ Assert.Equal("key", eventField.Key);
+ Assert.Equal("value", eventField.VStr);
+ eventField = eventFields[1];
+ Assert.Equal("description", eventField.Key);
+ Assert.Equal("Event1", eventField.VStr);
+
+ jaegerLog = logs[1];
+ Assert.Equal(events.Events.First().Timestamp.ToEpochMicroseconds(), jaegerLog.Timestamp);
+ Assert.Equal(jaegerLog.Fields.Count(), 2);
+ eventFields = jaegerLog.Fields.ToArray();
+ eventField = eventFields[0];
+ Assert.Equal("key", eventField.Key);
+ Assert.Equal("value", eventField.VStr);
+ eventField = eventFields[1];
+ Assert.Equal("description", eventField.Key);
+ Assert.Equal("Event2", eventField.VStr);
+ }
+
+ [Fact]
+ public void JaegerSpanConverterTest_ConvertSpanToJaegerSpan_NoEvents()
+ {
+ var startTimestamp = DateTime.Now;
+ var endTimestamp = startTimestamp.AddSeconds(60);
+ var eventTimestamp = DateTime.Now;
+
+ var traceId = ActivityTraceId.CreateRandom();
+ var traceIdAsInt = new Int128(traceId);
+ var spanId = ActivitySpanId.CreateRandom();
+ var spanIdAsInt = new Int128(spanId);
+ var parentSpanId = ActivitySpanId.CreateRandom();
+ var attributes = Attributes.Create(new Dictionary{
+ { "stringKey", "value"},
+ { "longKey", 1L},
+ { "longKey2", 1 },
+ { "doubleKey", 1D},
+ { "doubleKey2", 1F},
+ { "boolKey", true},
+ }, 0);
+
+ var linkedSpanId = ActivitySpanId.CreateRandom();
+
+ var link = Link.FromSpanContext(SpanContext.Create(
+ traceId,
+ linkedSpanId,
+ ActivityTraceFlags.Recorded,
+ Tracestate.Empty));
+
+ var linkTraceIdAsInt = new Int128(link.Context.TraceId);
+ var linkSpanIdAsInt = new Int128(link.Context.SpanId);
+
+ var links = LinkList.Create(new List { link }, 0);
+
+ var spanData = SpanData.Create(
+ SpanContext.Create(
+ traceId,
+ spanId,
+ ActivityTraceFlags.Recorded,
+ Tracestate.Empty
+ ),
+ parentSpanId,
+ Resource.Empty,
+ "Name",
+ startTimestamp,
+ attributes,
+ null,
+ links,
+ null,
+ Status.Ok,
+ SpanKind.Client,
+ endTimestamp
+ );
+
+ var jaegerSpan = spanData.ToJaegerSpan();
+
+ Assert.Equal("Name", jaegerSpan.OperationName);
+ Assert.Empty(jaegerSpan.Logs);
+
+ Assert.Equal(traceIdAsInt.High, jaegerSpan.TraceIdHigh);
+ Assert.Equal(traceIdAsInt.Low, jaegerSpan.TraceIdLow);
+ Assert.Equal(spanIdAsInt.Low, jaegerSpan.SpanId);
+ Assert.Equal(new Int128(parentSpanId).Low, jaegerSpan.ParentSpanId);
+
+ Assert.Equal(links.Links.Count(), jaegerSpan.References.Count());
+ var references = jaegerSpan.References.ToArray();
+ var jaegerRef = references[0];
+ Assert.Equal(linkTraceIdAsInt.High, jaegerRef.TraceIdHigh);
+ Assert.Equal(linkTraceIdAsInt.Low, jaegerRef.TraceIdLow);
+ Assert.Equal(linkSpanIdAsInt.Low, jaegerRef.SpanId);
+
+ Assert.Equal(0x1, jaegerSpan.Flags);
+
+ Assert.Equal(startTimestamp.ToEpochMicroseconds(), jaegerSpan.StartTime);
+ Assert.Equal(endTimestamp.ToEpochMicroseconds() - startTimestamp.ToEpochMicroseconds(), jaegerSpan.Duration);
+
+ var tags = jaegerSpan.JaegerTags.ToArray();
+ var tag = tags[0];
+ Assert.Equal(JaegerTagType.STRING, tag.VType);
+ Assert.Equal("stringKey", tag.Key);
+ Assert.Equal("value", tag.VStr);
+ tag = tags[1];
+ Assert.Equal(JaegerTagType.LONG, tag.VType);
+ Assert.Equal("longKey", tag.Key);
+ Assert.Equal(1, tag.VLong);
+ tag = tags[2];
+ Assert.Equal(JaegerTagType.LONG, tag.VType);
+ Assert.Equal("longKey2", tag.Key);
+ Assert.Equal(1, tag.VLong);
+ tag = tags[3];
+ Assert.Equal(JaegerTagType.DOUBLE, tag.VType);
+ Assert.Equal("doubleKey", tag.Key);
+ Assert.Equal(1, tag.VDouble);
+ tag = tags[4];
+ Assert.Equal(JaegerTagType.DOUBLE, tag.VType);
+ Assert.Equal("doubleKey2", tag.Key);
+ Assert.Equal(1, tag.VDouble);
+ tag = tags[5];
+ Assert.Equal(JaegerTagType.BOOL, tag.VType);
+ Assert.Equal("boolKey", tag.Key);
+ Assert.Equal(true, tag.VBool);
+ }
+
+ [Fact]
+ public void JaegerSpanConverterTest_ConvertSpanToJaegerSpan_NoLinks()
+ {
+ var startTimestamp = DateTime.Now;
+ var endTimestamp = startTimestamp.AddSeconds(60);
+ var eventTimestamp = DateTime.Now;
+
+ var traceId = ActivityTraceId.CreateRandom();
+ var traceIdAsInt = new Int128(traceId);
+ var spanId = ActivitySpanId.CreateRandom();
+ var spanIdAsInt = new Int128(spanId);
+ var parentSpanId = ActivitySpanId.CreateRandom();
+ var attributes = Attributes.Create(new Dictionary{
+ { "stringKey", "value"},
+ { "longKey", 1L},
+ { "longKey2", 1 },
+ { "doubleKey", 1D},
+ { "doubleKey2", 1F},
+ { "boolKey", true},
+ }, 0);
+ var events = TimedEvents.Create(new List>
+ {
+ TimedEvent.Create(
+ eventTimestamp,
+ Event.Create(
+ "Event1",
+ new Dictionary
+ {
+ { "key", "value" },
+ }
+ )
+ ),
+ TimedEvent.Create(
+ eventTimestamp,
+ Event.Create(
+ "Event2",
+ new Dictionary
+ {
+ { "key", "value" },
+ }
+ )
+ ),
+ }, 0);
+
+ var spanData = SpanData.Create(
+ SpanContext.Create(
+ traceId,
+ spanId,
+ ActivityTraceFlags.Recorded,
+ Tracestate.Empty
+ ),
+ parentSpanId,
+ Resource.Empty,
+ "Name",
+ startTimestamp,
+ attributes,
+ events,
+ null,
+ null,
+ Status.Ok,
+ SpanKind.Client,
+ endTimestamp
+ );
+
+ var jaegerSpan = spanData.ToJaegerSpan();
+
+ Assert.Equal("Name", jaegerSpan.OperationName);
+ Assert.Equal(2, jaegerSpan.Logs.Count());
+
+ Assert.Equal(traceIdAsInt.High, jaegerSpan.TraceIdHigh);
+ Assert.Equal(traceIdAsInt.Low, jaegerSpan.TraceIdLow);
+ Assert.Equal(spanIdAsInt.Low, jaegerSpan.SpanId);
+ Assert.Equal(new Int128(parentSpanId).Low, jaegerSpan.ParentSpanId);
+
+ Assert.Empty(jaegerSpan.References);
+
+ Assert.Equal(0x1, jaegerSpan.Flags);
+
+ Assert.Equal(startTimestamp.ToEpochMicroseconds(), jaegerSpan.StartTime);
+ Assert.Equal(endTimestamp.ToEpochMicroseconds() - startTimestamp.ToEpochMicroseconds(), jaegerSpan.Duration);
+
+ var tags = jaegerSpan.JaegerTags.ToArray();
+ var tag = tags[0];
+ Assert.Equal(JaegerTagType.STRING, tag.VType);
+ Assert.Equal("stringKey", tag.Key);
+ Assert.Equal("value", tag.VStr);
+ tag = tags[1];
+ Assert.Equal(JaegerTagType.LONG, tag.VType);
+ Assert.Equal("longKey", tag.Key);
+ Assert.Equal(1, tag.VLong);
+ tag = tags[2];
+ Assert.Equal(JaegerTagType.LONG, tag.VType);
+ Assert.Equal("longKey2", tag.Key);
+ Assert.Equal(1, tag.VLong);
+ tag = tags[3];
+ Assert.Equal(JaegerTagType.DOUBLE, tag.VType);
+ Assert.Equal("doubleKey", tag.Key);
+ Assert.Equal(1, tag.VDouble);
+ tag = tags[4];
+ Assert.Equal(JaegerTagType.DOUBLE, tag.VType);
+ Assert.Equal("doubleKey2", tag.Key);
+ Assert.Equal(1, tag.VDouble);
+ tag = tags[5];
+ Assert.Equal(JaegerTagType.BOOL, tag.VType);
+ Assert.Equal("boolKey", tag.Key);
+ Assert.Equal(true, tag.VBool);
+
+ var logs = jaegerSpan.Logs.ToArray();
+ var jaegerLog = logs[0];
+ Assert.Equal(events.Events.First().Timestamp.ToEpochMicroseconds(), jaegerLog.Timestamp);
+ Assert.Equal(2, jaegerLog.Fields.Count());
+ var eventFields = jaegerLog.Fields.ToArray();
+ var eventField = eventFields[0];
+ Assert.Equal("key", eventField.Key);
+ Assert.Equal("value", eventField.VStr);
+ eventField = eventFields[1];
+ Assert.Equal("description", eventField.Key);
+ Assert.Equal("Event1", eventField.VStr);
+
+ jaegerLog = logs[1];
+ Assert.Equal(events.Events.First().Timestamp.ToEpochMicroseconds(), jaegerLog.Timestamp);
+ Assert.Equal(jaegerLog.Fields.Count(), 2);
+ eventFields = jaegerLog.Fields.ToArray();
+ eventField = eventFields[0];
+ Assert.Equal("key", eventField.Key);
+ Assert.Equal("value", eventField.VStr);
+ eventField = eventFields[1];
+ Assert.Equal("description", eventField.Key);
+ Assert.Equal("Event2", eventField.VStr);
+ }
+ }
+}
diff --git a/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/JaegerThriftIntegrationTest.cs b/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/JaegerThriftIntegrationTest.cs
new file mode 100644
index 00000000000..947affc9c64
--- /dev/null
+++ b/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/JaegerThriftIntegrationTest.cs
@@ -0,0 +1,132 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Tests.Implementation
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Threading;
+ using OpenTelemetry.Exporter.Jaeger.Implementation;
+ using OpenTelemetry.Resources;
+ using OpenTelemetry.Trace;
+ using OpenTelemetry.Trace.Export;
+ using Thrift.Protocols;
+ using Xunit;
+ using Process = Jaeger.Implementation.Process;
+
+ public class JaegerThriftIntegrationTest
+ {
+ [Fact]
+ public async void JaegerThriftIntegrationTest_TAbstractBaseGeneratesConsistentThriftPayload()
+ {
+ var validJaegerThriftPayload = Convert.FromBase64String("goEBCWVtaXRCYXRjaBwcGAx0ZXN0IHByb2Nlc3MZHBgQdGVzdF9wcm9jZXNzX3RhZxUAGAp0ZXN0X3ZhbHVlAAAZHBbdlZjkk/C0+ZoBFtCr96fz8ZbpnQEW1KXb/ciQz6OBARaY3JDShc6hzrIBGAROYW1lGRwVABbdlZjkk/C0+ZoBFtCr96fz8ZbpnQEWkKas4ZbKtZyDAQAVAhaAgLPexpa/BRaAnJw5GWwYCXN0cmluZ0tleRUAGAV2YWx1ZQAYB2xvbmdLZXkVBkYCABgIbG9uZ0tleTIVBkYCABgJZG91YmxlS2V5FQInAAAAAAAA8D8AGApkb3VibGVLZXkyFQInAAAAAAAA8D8AGAdib29sS2V5FQQxABksFoCAs97Glr8FGSwYA2tleRUAGAV2YWx1ZQAYC2Rlc2NyaXB0aW9uFQAYBkV2ZW50MQAAFoCAs97Glr8FGSwYA2tleRUAGAV2YWx1ZQAYC2Rlc2NyaXB0aW9uFQAYBkV2ZW50MgAAAAAA");
+ using (var memoryTransport = new InMemoryTransport())
+ {
+ var protocolFactory = new TCompactProtocol.Factory();
+ var thriftClient = new JaegerThriftClient(protocolFactory.GetProtocol(memoryTransport));
+ var spanData = CreateTestSpan();
+ var span = spanData.ToJaegerSpan();
+ var process = new Process("test process", new Dictionary { { "test_process_tag", "test_value" } });
+ var batch = new Batch(process, new List { span });
+
+ await thriftClient.EmitBatchAsync(batch, CancellationToken.None);
+
+ var buff = memoryTransport.GetBuffer();
+
+ Assert.Equal(validJaegerThriftPayload, buff);
+ }
+ }
+
+
+ private SpanData CreateTestSpan()
+ {
+ var startTimestamp = new DateTime(2019, 1, 1);
+ var endTimestamp = startTimestamp.AddSeconds(60);
+ var eventTimestamp = new DateTime(2019, 1, 1);
+
+ var traceId = ActivityTraceId.CreateFromString("e8ea7e9ac72de94e91fabc613f9686b2".AsSpan());
+ var traceIdAsInt = new Int128(traceId);
+ var spanId = ActivitySpanId.CreateFromString("6a69db47429ea340".AsSpan());
+ var spanIdAsInt = new Int128(spanId);
+ var parentSpanId = ActivitySpanId.CreateFromBytes(new byte []{ 12, 23, 34, 45, 56, 67, 78, 89 });
+ var attributes = Attributes.Create(new Dictionary{
+ { "stringKey", "value"},
+ { "longKey", 1L},
+ { "longKey2", 1 },
+ { "doubleKey", 1D},
+ { "doubleKey2", 1F},
+ { "boolKey", true},
+ }, 0);
+ var events = TimedEvents.Create(new List>
+ {
+ TimedEvent.Create(
+ eventTimestamp,
+ Event.Create(
+ "Event1",
+ new Dictionary
+ {
+ { "key", "value" },
+ }
+ )
+ ),
+ TimedEvent.Create(
+ eventTimestamp,
+ Event.Create(
+ "Event2",
+ new Dictionary
+ {
+ { "key", "value" },
+ }
+ )
+ ),
+ }, 0);
+
+ var linkedSpanId = ActivitySpanId.CreateFromString("888915b6286b9c41".AsSpan());
+
+ var link = Link.FromSpanContext(SpanContext.Create(
+ traceId,
+ linkedSpanId,
+ ActivityTraceFlags.Recorded,
+ Tracestate.Empty));
+
+ var linkTraceIdAsInt = new Int128(link.Context.TraceId);
+ var linkSpanIdAsInt = new Int128(link.Context.SpanId);
+
+ var links = LinkList.Create(new List { link }, 0);
+
+ return SpanData.Create(
+ SpanContext.Create(
+ traceId,
+ spanId,
+ ActivityTraceFlags.Recorded,
+ Tracestate.Empty
+ ),
+ parentSpanId,
+ Resource.Empty,
+ "Name",
+ startTimestamp,
+ attributes,
+ events,
+ links,
+ null,
+ Status.Ok,
+ SpanKind.Client,
+ endTimestamp
+ );
+ }
+ }
+}
diff --git a/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/ThriftUdpClientTransportTests.cs b/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/ThriftUdpClientTransportTests.cs
new file mode 100644
index 00000000000..8c84f808f6a
--- /dev/null
+++ b/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/ThriftUdpClientTransportTests.cs
@@ -0,0 +1,167 @@
+//
+// Copyright 2018, OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+namespace OpenTelemetry.Exporter.Jaeger.Tests.Implementation
+{
+ using System;
+ using System.IO;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Moq;
+ using OpenTelemetry.Exporter.Jaeger.Implementation;
+ using Thrift.Transports;
+ using Xunit;
+
+ public class ThriftUdpClientTransportTests: IDisposable
+ {
+ private MemoryStream testingMemoryStream = new MemoryStream();
+ private readonly Mock mockClient = new Mock();
+
+ public void Dispose()
+ {
+ this.testingMemoryStream?.Dispose();
+ }
+
+ [Fact]
+ public void Constructor_ShouldConnectClient()
+ {
+ var host = "host, yo";
+ var port = 4528;
+
+ new JaegerThriftClientTransport(host, port, this.testingMemoryStream, this.mockClient.Object);
+
+ this.mockClient.Verify(t => t.Connect(host, port), Times.Once);
+ }
+
+ [Fact]
+ public void Close_ShouldCloseClient()
+ {
+ var host = "host, yo";
+ var port = 4528;
+
+ var transport = new JaegerThriftClientTransport(host, port, this.testingMemoryStream, this.mockClient.Object);
+ transport.Close();
+
+ this.mockClient.Verify(t => t.Close(), Times.Once);
+ }
+
+ [Fact]
+ public async Task ReadAsync_ShouldResultInNotImplementedException()
+ {
+ var host = "host, yo";
+ var port = 4528;
+
+ var transport = new JaegerThriftClientTransport(host, port, this.testingMemoryStream, this.mockClient.Object);
+ var newBuffer = new byte[8];
+
+ await Assert.ThrowsAsync(async () => await transport.ReadAsync(newBuffer, 0, 7, CancellationToken.None));
+ }
+
+ [Fact]
+ public async Task WriteAsync_ShouldWriteToMemoryStream()
+ {
+ var host = "host, yo";
+ var port = 4528;
+ var writeBuffer = new byte[] { 0x20, 0x10, 0x40, 0x30, 0x18, 0x14, 0x10, 0x28 };
+ var readBuffer = new byte[8];
+
+ var transport = new JaegerThriftClientTransport(host, port, this.testingMemoryStream, this.mockClient.Object);
+
+ await transport.WriteAsync(writeBuffer, CancellationToken.None);
+ this.testingMemoryStream.Seek(0, SeekOrigin.Begin);
+ var size = await this.testingMemoryStream.ReadAsync(readBuffer, 0, 8, CancellationToken.None);
+
+ Assert.Equal(8, size);
+ Assert.Equal(writeBuffer, readBuffer);
+ }
+
+ [Fact]
+ public void FlushAsync_ShouldReturnWhenNothingIsInTheStream()
+ {
+ var host = "host, yo";
+ var port = 4528;
+
+ var transport = new JaegerThriftClientTransport(host, port, this.testingMemoryStream, this.mockClient.Object);
+ var tInfo = transport.FlushAsync();
+
+ Assert.True(tInfo.IsCompleted);
+ this.mockClient.Verify(t => t.SendAsync(It.IsAny(), It.IsAny()), Times.Never);
+ }
+
+ [Fact]
+ public void FlushAsync_ShouldSendStreamBytes()
+ {
+ var host = "host, yo";
+ var port = 4528;
+ var streamBytes = new byte[] { 0x20, 0x10, 0x40, 0x30, 0x18, 0x14, 0x10, 0x28 };
+ this.testingMemoryStream = new MemoryStream(streamBytes);
+
+ var transport = new JaegerThriftClientTransport(host, port, this.testingMemoryStream, this.mockClient.Object);
+ var tInfo = transport.FlushAsync();
+
+ Assert.True(tInfo.IsCompleted);
+ this.mockClient.Verify(t => t.SendAsync(It.IsAny(), 8), Times.Once);
+ }
+
+ [Fact]
+ public async Task FlushAsync_ShouldThrowWhenClientDoes()
+ {
+ var host = "host, yo";
+ var port = 4528;
+ var streamBytes = new byte[] { 0x20, 0x10, 0x40, 0x30, 0x18, 0x14, 0x10, 0x28 };
+ this.testingMemoryStream = new MemoryStream(streamBytes);
+
+ //this.mockClient.Setup(t => t.SendAsync(It.IsAny(), It.IsAny())).Throws("message, yo");
+ this.mockClient.Setup(t => t.SendAsync(It.IsAny(), It.IsAny())).Throws(new Exception("message, yo"));
+
+ var transport = new JaegerThriftClientTransport(host, port, this.testingMemoryStream, this.mockClient.Object);
+ var ex = await Assert.ThrowsAsync(() => transport.FlushAsync());
+
+ Assert.Equal("Cannot flush closed transport. message, yo", ex.Message);
+ }
+
+ [Fact]
+ public void Dispose_ShouldCloseClientAndDisposeMemoryStream()
+ {
+ var host = "host, yo";
+ var port = 4528;
+
+ var transport = new JaegerThriftClientTransport(host, port, this.testingMemoryStream, this.mockClient.Object);
+ transport.Dispose();
+
+ this.mockClient.Verify(t => t.Dispose(), Times.Once);
+ Assert.False(this.testingMemoryStream.CanRead);
+ Assert.False(this.testingMemoryStream.CanSeek);
+ Assert.False(this.testingMemoryStream.CanWrite);
+ }
+
+ [Fact]
+ public void Dispose_ShouldNotTryToDisposeResourcesMoreThanOnce()
+ {
+ var host = "host, yo";
+ var port = 4528;
+
+ var transport = new JaegerThriftClientTransport(host, port, this.testingMemoryStream, this.mockClient.Object);
+ transport.Dispose();
+ transport.Dispose();
+
+ this.mockClient.Verify(t => t.Dispose(), Times.Once);
+ Assert.False(this.testingMemoryStream.CanRead);
+ Assert.False(this.testingMemoryStream.CanSeek);
+ Assert.False(this.testingMemoryStream.CanWrite);
+ }
+ }
+}
diff --git a/test/OpenTelemetry.Exporter.Jaeger.Tests/OpenTelemetry.Exporter.Jaeger.Tests.csproj b/test/OpenTelemetry.Exporter.Jaeger.Tests/OpenTelemetry.Exporter.Jaeger.Tests.csproj
new file mode 100644
index 00000000000..0f995164e4b
--- /dev/null
+++ b/test/OpenTelemetry.Exporter.Jaeger.Tests/OpenTelemetry.Exporter.Jaeger.Tests.csproj
@@ -0,0 +1,31 @@
+
+
+ Unit test project for Stackdriver Exporter for OpenTelemetry
+ net46;netcoreapp2.1
+ netcoreapp2.1
+ false
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
+
+
+
+
+
+
+
diff --git a/test/OpenTelemetry.Exporter.Jaeger.Tests/xunit.runner.json b/test/OpenTelemetry.Exporter.Jaeger.Tests/xunit.runner.json
new file mode 100644
index 00000000000..9fbc9011580
--- /dev/null
+++ b/test/OpenTelemetry.Exporter.Jaeger.Tests/xunit.runner.json
@@ -0,0 +1,4 @@
+{
+ "maxParallelThreads": 1,
+ "parallelizeTestCollections": false
+}
\ No newline at end of file