Skip to content

Commit 02d20a4

Browse files
vandonrbouwkast
andauthored
Add Google Protobuf Instrumentation (#6166)
## Summary of changes Adds a new instrumentation for Google Protobuf (not https://github.com/protobuf-net/protobuf-net) The instrumentation doesn't create any new spans, we are just adding tags on existing ones. The main one being a tag containing the full protobuf schema as an OpenAPI json. Since extracting the schema is costly, we only do it once every 30 seconds (hardcoded for now). ## Reason for change On request from DSM team, as an equivalent of its java counterpart: https://github.com/DataDog/dd-trace-java/tree/master/dd-java-agent/instrumentation/protobuf ## Implementation details - There is a cache <schema name -> schema json>, but I wanted to control the size, so I ended up repurposing the code that had been written for `DbConnectionCache`, where it's using a cache for small cardinality, but if there are too many different values, it stops caching. - I wrote a rate limiter rather than reusing one because to avoid forcing the sampling decision on every span, I needed one where I can peek the rate limit decision without "consuming" it. ## Test coverage Added a sample app that serializes and deserializes a protobuf message. Spans are created manually since this integration doesn't create one. ## Other details <!-- Fixes #{issue} --> <!-- ⚠️ Note: where possible, please obtain 2 approvals prior to merging. Unless CODEOWNERS specifies otherwise, for external teams it is typically best to have one review from a team member, and one review from apm-dotnet. Trivial changes do not require 2 reviews. --> --------- Co-authored-by: Steven Bouwkamp <steven.bouwkamp@datadoghq.com>
1 parent 3a9410a commit 02d20a4

File tree

59 files changed

+1830
-540
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1830
-540
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,9 @@ BenchmarkDotNet.Artifacts/
326326
# NServiceBus files
327327
.learningtransport
328328

329+
# protobuf file generate on compile
330+
tracer/test/test-applications/integrations/Samples.GoogleProtobuf/Sample.cs
331+
329332
## Tracer .gitignore settings
330333
# Tracer deployment artifacts
331334
tracer/deploy/linux/

Datadog.Trace.OSX.slnf

+1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
"tracer\\test\\test-applications\\integrations\\Samples.Elasticsearch.V7\\Samples.Elasticsearch.V7.csproj",
102102
"tracer\\test\\test-applications\\integrations\\Samples.Elasticsearch\\Samples.Elasticsearch.csproj",
103103
"tracer\\test\\test-applications\\integrations\\Samples.FakeDbCommand\\Samples.FakeDbCommand.csproj",
104+
"tracer\\test\\test-applications\\integrations\\Samples.GoogleProtobuf\\Samples.GoogleProtobuf.csproj",
104105
"tracer\\test\\test-applications\\integrations\\Samples.GraphQL3\\Samples.GraphQL3.csproj",
105106
"tracer\\test\\test-applications\\integrations\\Samples.GraphQL4\\Samples.GraphQL4.csproj",
106107
"tracer\\test\\test-applications\\integrations\\Samples.GraphQL7\\Samples.GraphQL7.csproj",

Datadog.Trace.Samples.g.sln

+7
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.AzureFunctions.V4Is
433433
EndProject
434434
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.AzureFunctions.V4Isolated.AspNetCore", "tracer\test\test-applications\azure-functions\Samples.AzureFunctions.V4Isolated.AspNetCore\Samples.AzureFunctions.V4Isolated.AspNetCore.csproj", "{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77}"
435435
EndProject
436+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.GoogleProtobuf", "tracer\test\test-applications\integrations\Samples.GoogleProtobuf\Samples.GoogleProtobuf.csproj", "{EF8C4CCE-E79C-4D78-BF31-222A11E198B9}"
437+
EndProject
436438
Global
437439
GlobalSection(SolutionConfigurationPlatforms) = preSolution
438440
Debug|Any CPU = Debug|Any CPU
@@ -1039,6 +1041,10 @@ Global
10391041
{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77}.Debug|Any CPU.Build.0 = Debug|Any CPU
10401042
{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77}.Release|Any CPU.ActiveCfg = Release|Any CPU
10411043
{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77}.Release|Any CPU.Build.0 = Release|Any CPU
1044+
{EF8C4CCE-E79C-4D78-BF31-222A11E198B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1045+
{EF8C4CCE-E79C-4D78-BF31-222A11E198B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
1046+
{EF8C4CCE-E79C-4D78-BF31-222A11E198B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
1047+
{EF8C4CCE-E79C-4D78-BF31-222A11E198B9}.Release|Any CPU.Build.0 = Release|Any CPU
10421048
EndGlobalSection
10431049
GlobalSection(NestedProjects) = preSolution
10441050
{9518425A-36A5-4B8F-B0B8-6137DB88441D} = {8CEC2042-F11C-49F5-A674-2355793B600A}
@@ -1211,5 +1217,6 @@ Global
12111217
{18767A3E-9ADC-485C-A8C7-50660D5B579D} = {C4C1E313-C7C1-4490-AECE-0DD0062380A4}
12121218
{5D2C6B9C-FCE2-4E46-B4ED-BC3B11CFBB3C} = {C4C1E313-C7C1-4490-AECE-0DD0062380A4}
12131219
{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77} = {C4C1E313-C7C1-4490-AECE-0DD0062380A4}
1220+
{EF8C4CCE-E79C-4D78-BF31-222A11E198B9} = {BAF8F246-3645-42AD-B1D0-0F7EAFBAB34A}
12141221
EndGlobalSection
12151222
EndGlobal

Datadog.Trace.sln

+7
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.AzureFunctions.V4Is
603603
EndProject
604604
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.AzureFunctions.V4Isolated.AspNetCore", "tracer\test\test-applications\azure-functions\Samples.AzureFunctions.V4Isolated.AspNetCore\Samples.AzureFunctions.V4Isolated.AspNetCore.csproj", "{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77}"
605605
EndProject
606+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.GoogleProtobuf", "tracer\test\test-applications\integrations\Samples.GoogleProtobuf\Samples.GoogleProtobuf.csproj", "{EF8C4CCE-E79C-4D78-BF31-222A11E198B9}"
607+
EndProject
606608
Global
607609
GlobalSection(SolutionConfigurationPlatforms) = preSolution
608610
Debug|Any CPU = Debug|Any CPU
@@ -1447,6 +1449,10 @@ Global
14471449
{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77}.Debug|Any CPU.Build.0 = Debug|Any CPU
14481450
{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77}.Release|Any CPU.ActiveCfg = Release|Any CPU
14491451
{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77}.Release|Any CPU.Build.0 = Release|Any CPU
1452+
{EF8C4CCE-E79C-4D78-BF31-222A11E198B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1453+
{EF8C4CCE-E79C-4D78-BF31-222A11E198B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
1454+
{EF8C4CCE-E79C-4D78-BF31-222A11E198B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
1455+
{EF8C4CCE-E79C-4D78-BF31-222A11E198B9}.Release|Any CPU.Build.0 = Release|Any CPU
14501456
EndGlobalSection
14511457
GlobalSection(SolutionProperties) = preSolution
14521458
HideSolutionNode = FALSE
@@ -1682,6 +1688,7 @@ Global
16821688
{18767A3E-9ADC-485C-A8C7-50660D5B579D} = {C4C1E313-C7C1-4490-AECE-0DD0062380A4}
16831689
{5D2C6B9C-FCE2-4E46-B4ED-BC3B11CFBB3C} = {C4C1E313-C7C1-4490-AECE-0DD0062380A4}
16841690
{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77} = {C4C1E313-C7C1-4490-AECE-0DD0062380A4}
1691+
{EF8C4CCE-E79C-4D78-BF31-222A11E198B9} = {BAF8F246-3645-42AD-B1D0-0F7EAFBAB34A}
16851692
EndGlobalSection
16861693
GlobalSection(ExtensibilityGlobals) = postSolution
16871694
SolutionGuid = {160A1D00-1F5B-40F8-A155-621B4459D78F}

docs/development/DuckTyping.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ DuckType.CreateTypeResult proxyResult = DuckType.GetOrCreateProxyType(proxyTarge
315315
if (proxyResult.Success)
316316
{
317317
// Pass in null, as there's no "instance" to duck type here
318-
return proxyResult.CreateInstance(null);
318+
return proxyResult.CreateInstance(null!);
319319
}
320320
else
321321
{
@@ -355,7 +355,7 @@ DuckType.CreateTypeResult proxyResult = DuckType.GetOrCreateProxyType(proxyType,
355355
if (proxyResult.Success)
356356
{
357357
// Pass in null, as there's no "instance" to duck type here, to create an instance of our proxy
358-
var proxy = (IObjectFactoryProxy)proxyResult.CreateInstance(null);
358+
var proxy = (IObjectFactoryProxy)proxyResult.CreateInstance(null!);
359359

360360
// invoke methods on the proxy
361361
object obj = proxy.CreateObject();

tracer/build/PackageVersionsGeneratorDefinitions.json

+10
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,16 @@
230230
"ExcludeTargetFrameworks": ["netcoreapp2.1", "netcoreapp3.0"]
231231
}]
232232
},
233+
{
234+
"IntegrationName": "Protobuf",
235+
"SampleProjectName": "Samples.GoogleProtobuf",
236+
"NugetPackageSearchName": "Google.Protobuf",
237+
"MinVersion": "3.0.0",
238+
"MaxVersionExclusive": "4.0.0",
239+
"SpecificVersions": [
240+
"3.*.*"
241+
]
242+
},
233243
{
234244
"IntegrationName": "RabbitMQ",
235245
"SampleProjectName": "Samples.RabbitMQ",

tracer/build/_build/Build.ExplorationTests.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,13 @@ public static ExplorationTestDescription GetExplorationTestDescription(Explorati
645645
"Google.Protobuf.CodedInputStreamTest.MaliciousRecursion",
646646
"Google.Protobuf.CodedInputStreamTest.MaliciousRecursion_UnknownFields",
647647
"Google.Protobuf.CodedInputStreamTest.RecursionLimitAppliedWhileSkippingGroup",
648-
"Google.Protobuf.JsonParserTest.MaliciousRecursion"
648+
"Google.Protobuf.JsonParserTest.MaliciousRecursion",
649+
// exclude those "legacy" tests because they are on manually modified code
650+
// that throws a NotImplementedException on the `Descriptor` property that we use.
651+
"Google.Protobuf.LegacyGeneratedCodeTest.IntermixingOfNewAndLegacyGeneratedCodeWorksWithCodedInputStream",
652+
"Google.Protobuf.LegacyGeneratedCodeTest.IntermixingOfNewAndLegacyGeneratedCodeWorksWithCodedOutputStream",
653+
"Google.Protobuf.LegacyGeneratedCodeTest.LegacyGeneratedCodeThrowsWithIBufferWriter",
654+
"Google.Protobuf.LegacyGeneratedCodeTest.LegacyGeneratedCodeThrowsWithReadOnlySequence"
649655
},
650656
LineProbesEnabled = true
651657
},

tracer/build/supported_calltargets.g.json

+96
Original file line numberDiff line numberDiff line change
@@ -11743,6 +11743,102 @@
1174311743
"IsAdoNetIntegration": false,
1174411744
"InstrumentationCategory": 1
1174511745
},
11746+
{
11747+
"IntegrationName": "Protobuf",
11748+
"AssemblyName": "Google.Protobuf",
11749+
"TargetTypeName": "Google.Protobuf.IBufferMessage",
11750+
"TargetMethodName": "InternalMergeFrom",
11751+
"TargetReturnType": "System.Void",
11752+
"TargetParameterTypes": [
11753+
"Google.Protobuf.ParseContext&"
11754+
],
11755+
"MinimumVersion": {
11756+
"Item1": 3,
11757+
"Item2": 15,
11758+
"Item3": 0
11759+
},
11760+
"MaximumVersion": {
11761+
"Item1": 3,
11762+
"Item2": 65535,
11763+
"Item3": 65535
11764+
},
11765+
"InstrumentationTypeName": "Datadog.Trace.ClrProfiler.AutoInstrumentation.Protobuf.BufferMessageInternalMergeFromIntegration",
11766+
"IntegrationKind": 2,
11767+
"IsAdoNetIntegration": false,
11768+
"InstrumentationCategory": 1
11769+
},
11770+
{
11771+
"IntegrationName": "Protobuf",
11772+
"AssemblyName": "Google.Protobuf",
11773+
"TargetTypeName": "Google.Protobuf.IBufferMessage",
11774+
"TargetMethodName": "InternalWriteTo",
11775+
"TargetReturnType": "System.Void",
11776+
"TargetParameterTypes": [
11777+
"Google.Protobuf.WriteContext&"
11778+
],
11779+
"MinimumVersion": {
11780+
"Item1": 3,
11781+
"Item2": 15,
11782+
"Item3": 0
11783+
},
11784+
"MaximumVersion": {
11785+
"Item1": 3,
11786+
"Item2": 65535,
11787+
"Item3": 65535
11788+
},
11789+
"InstrumentationTypeName": "Datadog.Trace.ClrProfiler.AutoInstrumentation.Protobuf.BufferMessageInternalWriteToIntegration",
11790+
"IntegrationKind": 2,
11791+
"IsAdoNetIntegration": false,
11792+
"InstrumentationCategory": 1
11793+
},
11794+
{
11795+
"IntegrationName": "Protobuf",
11796+
"AssemblyName": "Google.Protobuf",
11797+
"TargetTypeName": "Google.Protobuf.IMessage",
11798+
"TargetMethodName": "MergeFrom",
11799+
"TargetReturnType": "System.Void",
11800+
"TargetParameterTypes": [
11801+
"Google.Protobuf.CodedInputStream"
11802+
],
11803+
"MinimumVersion": {
11804+
"Item1": 3,
11805+
"Item2": 0,
11806+
"Item3": 0
11807+
},
11808+
"MaximumVersion": {
11809+
"Item1": 3,
11810+
"Item2": 65535,
11811+
"Item3": 65535
11812+
},
11813+
"InstrumentationTypeName": "Datadog.Trace.ClrProfiler.AutoInstrumentation.Protobuf.MessageMergeFromIntegration",
11814+
"IntegrationKind": 2,
11815+
"IsAdoNetIntegration": false,
11816+
"InstrumentationCategory": 1
11817+
},
11818+
{
11819+
"IntegrationName": "Protobuf",
11820+
"AssemblyName": "Google.Protobuf",
11821+
"TargetTypeName": "Google.Protobuf.IMessage",
11822+
"TargetMethodName": "WriteTo",
11823+
"TargetReturnType": "System.Void",
11824+
"TargetParameterTypes": [
11825+
"Google.Protobuf.CodedOutputStream"
11826+
],
11827+
"MinimumVersion": {
11828+
"Item1": 3,
11829+
"Item2": 0,
11830+
"Item3": 0
11831+
},
11832+
"MaximumVersion": {
11833+
"Item1": 3,
11834+
"Item2": 65535,
11835+
"Item3": 65535
11836+
},
11837+
"InstrumentationTypeName": "Datadog.Trace.ClrProfiler.AutoInstrumentation.Protobuf.MessageWriteToIntegration",
11838+
"IntegrationKind": 2,
11839+
"IsAdoNetIntegration": false,
11840+
"InstrumentationCategory": 1
11841+
},
1174611842
{
1174711843
"IntegrationName": "RabbitMQ",
1174811844
"AssemblyName": "RabbitMQ.Client",

tracer/missing-nullability-files.csv

-1
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,6 @@ src/Datadog.Trace/Util/ArraySlice.cs
226226
src/Datadog.Trace/Util/AsyncUtil.cs
227227
src/Datadog.Trace/Util/Clock.cs
228228
src/Datadog.Trace/Util/DomainMetadata.cs
229-
src/Datadog.Trace/Util/FnvHash64.cs
230229
src/Datadog.Trace/Util/IClock.cs
231230
src/Datadog.Trace/Util/IRandomIdGenerator.cs
232231
src/Datadog.Trace/Util/RandomIdGenerator.Net6.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// <copyright file="BufferMessageInternalMergeFromIntegration.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#nullable enable
7+
8+
using System;
9+
using System.ComponentModel;
10+
using Datadog.Trace.ClrProfiler.CallTarget;
11+
12+
namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.Protobuf;
13+
14+
/// <summary>
15+
/// System.Void Google.Protobuf.IBufferMessage::InternalMergeFrom(Google.Protobuf.ParseContext) calltarget instrumentation
16+
/// </summary>
17+
[InstrumentMethod(
18+
AssemblyName = "Google.Protobuf",
19+
TypeName = "Google.Protobuf.IBufferMessage",
20+
MethodName = "InternalMergeFrom",
21+
ParameterTypeNames = ["Google.Protobuf.ParseContext&"],
22+
ReturnTypeName = ClrNames.Void,
23+
MinimumVersion = "3.15.0",
24+
MaximumVersion = "3.*.*",
25+
IntegrationName = nameof(Configuration.IntegrationId.Protobuf),
26+
CallTargetIntegrationKind = CallTargetKind.Interface)]
27+
[Browsable(false)]
28+
[EditorBrowsable(EditorBrowsableState.Never)]
29+
public class BufferMessageInternalMergeFromIntegration
30+
{
31+
internal static CallTargetState OnMethodBegin<TTarget, TOutput>(TTarget instance, ref TOutput? output)
32+
where TTarget : IMessageProxy
33+
{
34+
if (Helper.TryGetDescriptor(instance, out var descriptor))
35+
{
36+
SchemaExtractor.EnrichActiveSpanWith(descriptor, "deserialization");
37+
}
38+
39+
return CallTargetState.GetDefault();
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// <copyright file="BufferMessageInternalWriteToIntegration.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#nullable enable
7+
8+
using System.ComponentModel;
9+
using Datadog.Trace.ClrProfiler.CallTarget;
10+
11+
namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.Protobuf;
12+
13+
/// <summary>
14+
/// System.Void Google.Protobuf.IBufferMessage::InternalWriteTo(Google.Protobuf.WriteContext) calltarget instrumentation
15+
/// </summary>
16+
[InstrumentMethod(
17+
AssemblyName = "Google.Protobuf",
18+
TypeName = "Google.Protobuf.IBufferMessage",
19+
MethodName = "InternalWriteTo",
20+
ReturnTypeName = ClrNames.Void,
21+
ParameterTypeNames = ["Google.Protobuf.WriteContext&"],
22+
MinimumVersion = "3.15.0",
23+
MaximumVersion = "3.*.*",
24+
IntegrationName = nameof(Configuration.IntegrationId.Protobuf),
25+
CallTargetIntegrationKind = CallTargetKind.Interface)]
26+
[Browsable(false)]
27+
[EditorBrowsable(EditorBrowsableState.Never)]
28+
public class BufferMessageInternalWriteToIntegration
29+
{
30+
internal static CallTargetState OnMethodBegin<TTarget, TCtx>(TTarget instance, ref TCtx ctx)
31+
where TTarget : IMessageProxy
32+
{
33+
if (instance.Instance is not null)
34+
{
35+
SchemaExtractor.EnrichActiveSpanWith(instance.Descriptor, "deserialization");
36+
}
37+
38+
return CallTargetState.GetDefault();
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// <copyright file="Helper.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#nullable enable
7+
8+
using System;
9+
using System.Threading;
10+
using Datadog.Trace.DuckTyping;
11+
using Datadog.Trace.Logging;
12+
13+
namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.Protobuf;
14+
15+
internal class Helper
16+
{
17+
private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor<Helper>();
18+
19+
private static readonly Lazy<IDescriptorReflectionProxy?> DescriptorReflectionProxy = new(
20+
() =>
21+
{
22+
// prepare incantations to access the static property DescriptorReflection.Descriptor
23+
var staticType = Type.GetType("Google.Protobuf.Reflection.DescriptorReflection,Google.Protobuf");
24+
if (staticType == null)
25+
{
26+
return null;
27+
}
28+
29+
var proxyType = typeof(IDescriptorReflectionProxy);
30+
31+
var proxyResult = DuckType.GetOrCreateProxyType(proxyType, staticType);
32+
if (!proxyResult.Success)
33+
{
34+
Log.Warning("Cannot create proxy for type Google.Protobuf.Reflection.DescriptorReflection, protobuf instrumentation may malfunction.");
35+
return null;
36+
}
37+
38+
return (IDescriptorReflectionProxy)proxyResult.CreateInstance(null!);
39+
});
40+
41+
public interface IDescriptorReflectionProxy
42+
{
43+
object? Descriptor { get; } // this is actually a static property
44+
}
45+
46+
public static bool TryGetDescriptor(IMessageProxy messageProxy, out MessageDescriptorProxy? descriptor)
47+
{
48+
descriptor = null;
49+
if (messageProxy.Instance is null)
50+
{
51+
return false;
52+
}
53+
54+
// some public functions that we are instrumenting are also called internally by protobuf,
55+
// and there is one case where trying to access the descriptor at that point results in a nullref
56+
// because it relies on this property. We check it here to make sure we're not going to generate an exception by accessing it.
57+
if (DescriptorReflectionProxy.Value?.Descriptor == null)
58+
{
59+
return false;
60+
}
61+
62+
// now we know it's safe to access the Descriptor property on the message
63+
descriptor = messageProxy.Descriptor;
64+
return true;
65+
}
66+
}

0 commit comments

Comments
 (0)