diff --git a/Directory.Packages.props b/Directory.Packages.props
index 38a02aab77e..39c9d1897d6 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -54,7 +54,7 @@
-
+
@@ -93,7 +93,7 @@
-
+
diff --git a/Orleans.sln b/Orleans.sln
index 281379e1c27..802adea46c6 100644
--- a/Orleans.sln
+++ b/Orleans.sln
@@ -212,6 +212,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Orleans.GrainDirectory.Redi
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tester.Redis", "test\Extensions\Tester.Redis\Tester.Redis.csproj", "{F13247A0-70C9-4200-9CB1-2002CB8105E0}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Orleans.Serialization.Protobuf", "src\Serializers\Orleans.Serialization.Protobuf\Orleans.Serialization.Protobuf.csproj", "{A073C0EE-8732-42F9-A22E-D47034E25076}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -466,14 +468,6 @@ Global
{16B9B850-ED3B-4B45-B0F2-3F802D44F382}.Debug|Any CPU.Build.0 = Debug|Any CPU
{16B9B850-ED3B-4B45-B0F2-3F802D44F382}.Release|Any CPU.ActiveCfg = Release|Any CPU
{16B9B850-ED3B-4B45-B0F2-3F802D44F382}.Release|Any CPU.Build.0 = Release|Any CPU
- {CCEF897C-F4F8-48F0-8F95-CC1487EE2936}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {CCEF897C-F4F8-48F0-8F95-CC1487EE2936}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {CCEF897C-F4F8-48F0-8F95-CC1487EE2936}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {CCEF897C-F4F8-48F0-8F95-CC1487EE2936}.Release|Any CPU.Build.0 = Release|Any CPU
- {F13247A0-70C9-4200-9CB1-2002CB8105E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {F13247A0-70C9-4200-9CB1-2002CB8105E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {F13247A0-70C9-4200-9CB1-2002CB8105E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {F13247A0-70C9-4200-9CB1-2002CB8105E0}.Release|Any CPU.Build.0 = Release|Any CPU
{D1214CD3-EB99-4420-9E30-A50ACFD66A48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D1214CD3-EB99-4420-9E30-A50ACFD66A48}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D1214CD3-EB99-4420-9E30-A50ACFD66A48}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -562,6 +556,18 @@ Global
{2268B639-02B8-4903-B719-65F7EBD05D52}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2268B639-02B8-4903-B719-65F7EBD05D52}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2268B639-02B8-4903-B719-65F7EBD05D52}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CCEF897C-F4F8-48F0-8F95-CC1487EE2936}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CCEF897C-F4F8-48F0-8F95-CC1487EE2936}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CCEF897C-F4F8-48F0-8F95-CC1487EE2936}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CCEF897C-F4F8-48F0-8F95-CC1487EE2936}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F13247A0-70C9-4200-9CB1-2002CB8105E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F13247A0-70C9-4200-9CB1-2002CB8105E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F13247A0-70C9-4200-9CB1-2002CB8105E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F13247A0-70C9-4200-9CB1-2002CB8105E0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A073C0EE-8732-42F9-A22E-D47034E25076}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A073C0EE-8732-42F9-A22E-D47034E25076}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A073C0EE-8732-42F9-A22E-D47034E25076}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A073C0EE-8732-42F9-A22E-D47034E25076}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -640,8 +646,6 @@ Global
{8E01A6EB-DE96-4DFD-AA7D-B07078F12372} = {FE2E08C6-9C3B-4AEE-AE07-CCA387580D7A}
{D53D80CC-3E14-4499-B03F-610A5D3F6359} = {A6573187-FD0D-4DF7-91D1-03E07E470C0A}
{16B9B850-ED3B-4B45-B0F2-3F802D44F382} = {4C5D66BF-EE1C-4DD8-8551-D1B7F3768A34}
- {CCEF897C-F4F8-48F0-8F95-CC1487EE2936} = {A734945A-36DC-485E-B84D-3C2D395BC7BE}
- {F13247A0-70C9-4200-9CB1-2002CB8105E0} = {082D25DB-70CA-48F4-93E0-EC3455F494B8}
{D1214CD3-EB99-4420-9E30-A50ACFD66A48} = {FE2E08C6-9C3B-4AEE-AE07-CCA387580D7A}
{65D8F6B3-DEE2-412B-95F2-77274461D58C} = {4CD3AA9E-D937-48CA-BB6C-158E12257D23}
{A145AFFC-E0CF-4861-AB0C-427C7670FF37} = {4CD3AA9E-D937-48CA-BB6C-158E12257D23}
@@ -666,6 +670,9 @@ Global
{671AE42C-974A-467B-BE89-0A3F706B5B21} = {A734945A-36DC-485E-B84D-3C2D395BC7BE}
{28B35216-0C6E-4CF1-8C14-7D9A4BE161A5} = {A734945A-36DC-485E-B84D-3C2D395BC7BE}
{2268B639-02B8-4903-B719-65F7EBD05D52} = {A734945A-36DC-485E-B84D-3C2D395BC7BE}
+ {CCEF897C-F4F8-48F0-8F95-CC1487EE2936} = {A734945A-36DC-485E-B84D-3C2D395BC7BE}
+ {F13247A0-70C9-4200-9CB1-2002CB8105E0} = {082D25DB-70CA-48F4-93E0-EC3455F494B8}
+ {A073C0EE-8732-42F9-A22E-D47034E25076} = {4CD3AA9E-D937-48CA-BB6C-158E12257D23}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7BFB3429-B5BB-4DB1-95B4-67D77A864952}
diff --git a/src/Serializers/Directory.Build.props b/src/Serializers/Directory.Build.props
deleted file mode 100644
index e28577bcfd1..00000000000
--- a/src/Serializers/Directory.Build.props
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
- <_ParentDirectoryBuildPropsPath Condition="'$(_DirectoryBuildPropsFile)' != ''">$([System.IO.Path]::Combine('..', '$(_DirectoryBuildPropsFile)'))
-
-
-
-
-
- false
-
-
-
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Serializers/Orleans.Serialization.Protobuf/Orleans.Serialization.Protobuf.csproj b/src/Serializers/Orleans.Serialization.Protobuf/Orleans.Serialization.Protobuf.csproj
index 05e2c729e56..25862014110 100644
--- a/src/Serializers/Orleans.Serialization.Protobuf/Orleans.Serialization.Protobuf.csproj
+++ b/src/Serializers/Orleans.Serialization.Protobuf/Orleans.Serialization.Protobuf.csproj
@@ -1,15 +1,11 @@
-
- Microsoft.Orleans.OrleansGoogleUtils
- Microsoft Orleans Google Utilities
- Library of utility types for Google of Microsoft Orleans.
- $(PackageTags) ProtoBuf
- $(DefaultTargetFrameworks)
-
- Orleans.Serialization.Protobuf
- OrleansGoogleUtils
+ Microsoft.Orleans.Serialization.Protobuf
+ $(DefaultTargetFrameworks);netstandard2.1
+ Google.Protobuf integration for Orleans.Serialization
+ true
+ false
@@ -17,4 +13,8 @@
+
+
+
+
diff --git a/src/Serializers/Orleans.Serialization.Protobuf/ProtobufCodec.cs b/src/Serializers/Orleans.Serialization.Protobuf/ProtobufCodec.cs
new file mode 100644
index 00000000000..2e0ebdc139b
--- /dev/null
+++ b/src/Serializers/Orleans.Serialization.Protobuf/ProtobufCodec.cs
@@ -0,0 +1,219 @@
+using Google.Protobuf;
+using Orleans.Serialization.Buffers;
+using Orleans.Serialization.Buffers.Adaptors;
+using Orleans.Serialization.Cloning;
+using Orleans.Serialization.Codecs;
+using Orleans.Serialization.Serializers;
+using Orleans.Serialization.WireProtocol;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Orleans.Serialization;
+
+[Alias(WellKnownAlias)]
+public sealed class ProtobufCodec : IGeneralizedCodec, IGeneralizedCopier, ITypeFilter
+{
+ public const string WellKnownAlias = "protobuf";
+
+ private static readonly Type SelfType = typeof(ProtobufCodec);
+ private static readonly ConcurrentDictionary MessageParsers = new();
+
+ private readonly ICodecSelector[] _serializableTypeSelectors;
+ private readonly ICopierSelector[] _copyableTypeSelectors;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Filters used to indicate which types should be serialized by this codec.
+ /// Filters used to indicate which types should be copied by this codec.
+ public ProtobufCodec(
+ IEnumerable serializableTypeSelectors,
+ IEnumerable copyableTypeSelectors)
+ {
+ _serializableTypeSelectors = serializableTypeSelectors.Where(t => string.Equals(t.CodecName, WellKnownAlias, StringComparison.Ordinal)).ToArray();
+ _copyableTypeSelectors = copyableTypeSelectors.Where(t => string.Equals(t.CopierName, WellKnownAlias, StringComparison.Ordinal)).ToArray();
+ }
+
+ ///
+ public object DeepCopy(object input, CopyContext context)
+ {
+ if (!context.TryGetCopy(input, out object result))
+ {
+ if (input is not IMessage protobufMessage)
+ {
+ throw new InvalidOperationException("Input is not a protobuf message");
+ }
+
+ var messageSize = protobufMessage.CalculateSize();
+ using var buffer = new PooledArrayBufferWriter();
+ var spanBuffer = buffer.GetSpan(messageSize)[..messageSize];
+ protobufMessage.WriteTo(spanBuffer);
+
+ result = protobufMessage.Descriptor.Parser.ParseFrom(spanBuffer);
+
+ context.RecordCopy(input, result);
+ }
+
+ return result;
+ }
+
+ ///
+ bool IGeneralizedCodec.IsSupportedType(Type type)
+ {
+ if (type == SelfType)
+ {
+ return true;
+ }
+
+ foreach (var selector in _serializableTypeSelectors)
+ {
+ if (selector.IsSupportedType(type))
+ {
+ return IsProtobufMessage(type);
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ bool IGeneralizedCopier.IsSupportedType(Type type)
+ {
+ foreach (var selector in _copyableTypeSelectors)
+ {
+ if (selector.IsSupportedType(type))
+ {
+ return IsProtobufMessage(type);
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ bool? ITypeFilter.IsTypeAllowed(Type type)
+ {
+ if (!typeof(IMessage).IsAssignableFrom(type))
+ {
+ return null;
+ }
+
+ return ((IGeneralizedCodec)this).IsSupportedType(type) || ((IGeneralizedCopier)this).IsSupportedType(type);
+ }
+
+ private static bool IsProtobufMessage(Type type)
+ {
+ if (!MessageParsers.ContainsKey(type.TypeHandle))
+ {
+ if (Activator.CreateInstance(type) is not IMessage protobufMessageInstance)
+ {
+ return false;
+ }
+
+ MessageParsers.TryAdd(type.TypeHandle, protobufMessageInstance.Descriptor.Parser);
+ }
+
+ return true;
+ }
+
+ ///
+ object IFieldCodec.ReadValue(ref Reader reader, Field field)
+ {
+ if (field.IsReference)
+ {
+ return ReferenceCodec.ReadReference(ref reader, field.FieldType);
+ }
+
+ field.EnsureWireTypeTagDelimited();
+
+ var placeholderReferenceId = ReferenceCodec.CreateRecordPlaceholder(reader.Session);
+ object result = null;
+ Type type = null;
+ uint fieldId = 0;
+
+ while (true)
+ {
+ var header = reader.ReadFieldHeader();
+ if (header.IsEndBaseOrEndObject)
+ {
+ break;
+ }
+
+ fieldId += header.FieldIdDelta;
+ switch (fieldId)
+ {
+ case 0:
+ ReferenceCodec.MarkValueField(reader.Session);
+ type = reader.Session.TypeCodec.ReadLengthPrefixed(ref reader);
+ break;
+ case 1:
+ if (type is null)
+ {
+ ThrowTypeFieldMissing();
+ }
+
+ if (!MessageParsers.TryGetValue(type.TypeHandle, out var messageParser))
+ {
+ throw new ArgumentException($"No parser found for the expected type {type.Name}", nameof(TInput));
+ }
+
+ ReferenceCodec.MarkValueField(reader.Session);
+ var length = (int)reader.ReadVarUInt32();
+
+ using (var buffer = new PooledArrayBufferWriter())
+ {
+ var spanBuffer = buffer.GetSpan(length)[..length];
+ reader.ReadBytes(spanBuffer);
+ result = messageParser.ParseFrom(spanBuffer);
+ }
+ break;
+ default:
+ reader.ConsumeUnknownField(header);
+ break;
+ }
+ }
+
+ ReferenceCodec.RecordObject(reader.Session, result, placeholderReferenceId);
+ return result;
+ }
+
+ private static void ThrowTypeFieldMissing() => throw new RequiredFieldMissingException("Serialized value is missing its type field.");
+
+ ///
+ void IFieldCodec.WriteField(ref Writer writer, uint fieldIdDelta, Type expectedType, object value)
+ {
+ if (ReferenceCodec.TryWriteReferenceField(ref writer, fieldIdDelta, expectedType, value))
+ {
+ return;
+ }
+
+ if (value is not IMessage protobufMessage)
+ {
+ throw new ArgumentException("The provided value for serialization in not an instance of IMessage");
+ }
+
+ writer.WriteFieldHeader(fieldIdDelta, expectedType, SelfType, WireType.TagDelimited);
+
+ // Write the type name
+ ReferenceCodec.MarkValueField(writer.Session);
+ writer.WriteFieldHeaderExpected(0, WireType.LengthPrefixed);
+ writer.Session.TypeCodec.WriteLengthPrefixed(ref writer, value.GetType());
+
+ var messageSize = protobufMessage.CalculateSize();
+
+ using var buffer = new PooledArrayBufferWriter();
+ var spanBuffer = buffer.GetSpan(messageSize)[..messageSize];
+
+ // Write the serialized payload
+ protobufMessage.WriteTo(spanBuffer);
+
+ ReferenceCodec.MarkValueField(writer.Session);
+ writer.WriteFieldHeaderExpected(1, WireType.LengthPrefixed);
+ writer.WriteVarUInt32((uint)spanBuffer.Length);
+ writer.Write(spanBuffer);
+
+ writer.WriteEndObject();
+ }
+}
diff --git a/src/Serializers/Orleans.Serialization.Protobuf/ProtobufSerializer.cs b/src/Serializers/Orleans.Serialization.Protobuf/ProtobufSerializer.cs
deleted file mode 100644
index 5824c72dfe6..00000000000
--- a/src/Serializers/Orleans.Serialization.Protobuf/ProtobufSerializer.cs
+++ /dev/null
@@ -1,118 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Reflection;
-using Google.Protobuf;
-
-namespace Orleans.Serialization
-{
- ///
- /// An implementation of IExternalSerializer for usage with Protobuf types.
- ///
- public class ProtobufSerializer : IExternalSerializer
- {
- private static readonly ConcurrentDictionary Parsers = new ConcurrentDictionary();
-
- ///
- /// Determines whether this serializer has the ability to serialize a particular type.
- ///
- /// The type of the item to be serialized
- /// A value indicating whether the type can be serialized
- public bool IsSupportedType(Type itemType)
- {
- if (typeof(IMessage).IsAssignableFrom(itemType))
- {
- if (!Parsers.ContainsKey(itemType.TypeHandle))
- {
- var prop = itemType.GetProperty("Parser", BindingFlags.Public | BindingFlags.Static);
- if (prop == null)
- {
- return false;
- }
-
- var parser = prop.GetValue(null, null);
- Parsers.TryAdd(itemType.TypeHandle, parser as MessageParser);
- }
- return true;
- }
- return false;
- }
-
- ///
- public object DeepCopy(object source, ICopyContext context)
- {
- if (source == null)
- {
- return null;
- }
-
- dynamic dynamicSource = source;
- return dynamicSource.Clone();
- }
-
- ///
- public void Serialize(object item, ISerializationContext context, Type expectedType)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- if (item == null)
- {
- // Special handling for null value.
- // Since in this ProtobufSerializer we are usually writing the data lengh as 4 bytes
- // we also have to write the Null object as 4 bytes lengh of zero.
- context.StreamWriter.Write(0);
- return;
- }
-
- IMessage iMessage = item as IMessage;
- if (iMessage == null)
- {
- throw new ArgumentException("The provided item for serialization in not an instance of " + typeof(IMessage), "item");
- }
- // The way we write the data is potentially in-efficinet,
- // since we are first writing to ProtoBuff's internal CodedOutputStream
- // and then take its internal byte[] and write it into out own BinaryTokenStreamWriter.
- // Writing byte[] to BinaryTokenStreamWriter may sometimes copy the byte[] and sometimes just append ass ArraySegment without copy.
- // In the former case it will be a secodnd copy.
- // It would be more effecient to write directly into BinaryTokenStreamWriter
- // but protobuff does not currently support writing directly into a given arbitary stream
- // (it does support System.IO.Steam but BinaryTokenStreamWriter is not compatible with System.IO.Steam).
- // Alternatively, we could force to always append to BinaryTokenStreamWriter, but that could create a lot of small ArraySegments.
- // The plan is to ask the ProtoBuff team to add support for some "InputStream" interface, like Bond does.
- byte[] outBytes = iMessage.ToByteArray();
- context.StreamWriter.Write(outBytes.Length);
- context.StreamWriter.Write(outBytes);
- }
-
- ///
- public object Deserialize(Type expectedType, IDeserializationContext context)
- {
- if (expectedType == null)
- {
- throw new ArgumentNullException(nameof(expectedType));
- }
-
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- var typeHandle = expectedType.TypeHandle;
- MessageParser parser;
- if (!Parsers.TryGetValue(typeHandle, out parser))
- {
- throw new ArgumentException("No parser found for the expected type " + expectedType, nameof(expectedType));
- }
-
- var reader = context.StreamReader;
- int length = reader.ReadInt();
- byte[] data = reader.ReadBytes(length);
-
- object message = parser.ParseFrom(data);
-
- return message;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Serializers/Orleans.Serialization.Protobuf/SerializationHostingExtensions.cs b/src/Serializers/Orleans.Serialization.Protobuf/SerializationHostingExtensions.cs
new file mode 100644
index 00000000000..e2b2b536add
--- /dev/null
+++ b/src/Serializers/Orleans.Serialization.Protobuf/SerializationHostingExtensions.cs
@@ -0,0 +1,70 @@
+using Google.Protobuf;
+using Microsoft.Extensions.DependencyInjection;
+using Orleans.Serialization.Cloning;
+using Orleans.Serialization.Serializers;
+using Orleans.Serialization.Utilities.Internal;
+using System;
+
+namespace Orleans.Serialization;
+
+///
+/// Extension method for .
+///
+public static class SerializationHostingExtensions
+{
+ private static readonly ServiceDescriptor ServiceDescriptor = new (typeof(ProtobufCodec), typeof(ProtobufCodec));
+
+ ///
+ /// Adds support for serializing and deserializing Protobuf IMessage types using .
+ ///
+ /// The serializer builder.
+ public static ISerializerBuilder AddProtobufSerializer(
+ this ISerializerBuilder serializerBuilder)
+ => serializerBuilder.AddProtobufSerializer(
+ isSerializable: type => typeof(IMessage).IsAssignableFrom(type),
+ isCopyable: type => typeof(IMessage).IsAssignableFrom(type));
+
+ ///
+ /// Adds support for serializing and deserializing Protobuf IMessage types using .
+ ///
+ /// The serializer builder.
+ /// A delegate used to indicate which types should be serialized by this codec.
+ /// A delegate used to indicate which types should be copied by this codec.
+ public static ISerializerBuilder AddProtobufSerializer(
+ this ISerializerBuilder serializerBuilder,
+ Func isSerializable,
+ Func isCopyable)
+ {
+ var services = serializerBuilder.Services;
+
+ if (isSerializable != null)
+ {
+ services.AddSingleton(new DelegateCodecSelector
+ {
+ CodecName = ProtobufCodec.WellKnownAlias,
+ IsSupportedTypeDelegate = isSerializable
+ });
+ }
+
+ if (isCopyable != null)
+ {
+ services.AddSingleton(new DelegateCopierSelector
+ {
+ CopierName = ProtobufCodec.WellKnownAlias,
+ IsSupportedTypeDelegate = isCopyable
+ });
+ }
+
+ if (!services.Contains(ServiceDescriptor))
+ {
+ services.AddSingleton();
+ services.AddFromExisting();
+ services.AddFromExisting();
+ services.AddFromExisting();
+
+ serializerBuilder.Configure(options => options.WellKnownTypeAliases[ProtobufCodec.WellKnownAlias] = typeof(ProtobufCodec));
+ }
+
+ return serializerBuilder;
+ }
+}
\ No newline at end of file
diff --git a/src/Serializers/Orleans.Serialization.ProtobufNet/Orleans.Serialization.ProtobufNet.csproj b/src/Serializers/Orleans.Serialization.ProtobufNet/Orleans.Serialization.ProtobufNet.csproj
deleted file mode 100644
index ed77c006c16..00000000000
--- a/src/Serializers/Orleans.Serialization.ProtobufNet/Orleans.Serialization.ProtobufNet.csproj
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
- Microsoft.Orleans.ProtobufNet
- Microsoft Orleans ProtobufNet
- Library of utility types for ProtobufNet of Microsoft Orleans.
- $(PackageTags) ProtoBuf protobuf-net
- $(DefaultTargetFrameworks)
-
-
-
- Orleans.Serialization.ProtobufNet
- ProtobufNetUtils
-
-
-
-
-
-
-
diff --git a/src/Serializers/Orleans.Serialization.ProtobufNet/ProtobufNetSerializer.cs b/src/Serializers/Orleans.Serialization.ProtobufNet/ProtobufNetSerializer.cs
deleted file mode 100644
index ee356096056..00000000000
--- a/src/Serializers/Orleans.Serialization.ProtobufNet/ProtobufNetSerializer.cs
+++ /dev/null
@@ -1,114 +0,0 @@
-using System;
-using System.Buffers;
-using System.Collections.Concurrent;
-using System.IO;
-using Orleans.Serialization.Cloning;
-using Orleans.Serialization.Serializers;
-using Orleans.Serialization.WireProtocol;
-
-namespace Orleans.Serialization.ProtobufNet
-{
- ///
- /// An implementation of IExternalSerializer for usage with Protobuf types, using the protobuf-net library.
- ///
- [WellKnownAlias("pb-net")]
- public class ProtobufNetSerializer : IGeneralizedCodec, IGeneralizedCopier
- {
- private static readonly ConcurrentDictionary Cache = new();
-
- ///
- /// Determines whether this serializer has the ability to serialize a particular type.
- ///
- /// The type of the item to be serialized
- /// A value indicating whether the type can be serialized
- public bool IsSupportedType(Type itemType)
- {
- if (Cache.TryGetValue(itemType.TypeHandle, out var cacheItem))
- {
- return cacheItem.IsSupported;
- }
-
- return Cache.GetOrAdd(itemType.TypeHandle, new ProtobufTypeCacheItem(itemType)).IsSupported;
- }
-
- public void WriteField(ref Buffers.Writer writer, uint fieldIdDelta, Type expectedType, object value) where TBufferWriter : IBufferWriter
- {
-
- }
-
- public object ReadValue(ref Buffers.Reader reader, Field field) => throw new NotImplementedException();
-
- ///
- public void Serialize(object item, ISerializationContext context, Type expectedType)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- if (item == null)
- {
- // Special handling for null value.
- // Since in this ProtobufSerializer we are usually writing the data lengh as 4 bytes
- // we also have to write the Null object as 4 bytes lengh of zero.
- context.StreamWriter.Write(0);
- return;
- }
-
- using (var stream = new MemoryStream())
- {
- ProtoBuf.Serializer.Serialize(stream, item);
- // The way we write the data is potentially in-efficinet,
- // since we are first writing to ProtoBuff's internal CodedOutputStream
- // and then take its internal byte[] and write it into out own BinaryTokenStreamWriter.
- // Writing byte[] to BinaryTokenStreamWriter may sometimes copy the byte[] and sometimes just append ass ArraySegment without copy.
- // In the former case it will be a secodnd copy.
- // It would be more effecient to write directly into BinaryTokenStreamWriter
- // but protobuff does not currently support writing directly into a given arbitary stream
- // (it does support System.IO.Steam but BinaryTokenStreamWriter is not compatible with System.IO.Steam).
- // Alternatively, we could force to always append to BinaryTokenStreamWriter, but that could create a lot of small ArraySegments.
- // The plan is to ask the ProtoBuff team to add support for some "InputStream" interface, like Bond does.
- byte[] outBytes = stream.ToArray();
- context.StreamWriter.Write(outBytes.Length);
- context.StreamWriter.Write(outBytes);
- }
- }
-
- ///
- public object Deserialize(Type expectedType, IDeserializationContext context)
- {
- if (expectedType == null)
- {
- throw new ArgumentNullException(nameof(expectedType));
- }
-
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- var reader = context.StreamReader;
- int length = reader.ReadInt();
- byte[] data = reader.ReadBytes(length);
-
- object message = null;
- using (var stream = new MemoryStream(data))
- {
- message = ProtoBuf.Serializer.Deserialize(expectedType, stream);
- }
-
- return message;
- }
-
- public object DeepCopy(object input, CopyContext context)
- {
- if (input == null)
- {
- return null;
- }
-
- var cacheItem = Cache[input.GetType().TypeHandle];
- return cacheItem.IsImmutable ? input : ProtoBuf.Serializer.DeepClone((object)input);
- }
- }
-}
diff --git a/src/Serializers/Orleans.Serialization.ProtobufNet/ProtobufTypeCacheItem.cs b/src/Serializers/Orleans.Serialization.ProtobufNet/ProtobufTypeCacheItem.cs
deleted file mode 100644
index 745949bf52e..00000000000
--- a/src/Serializers/Orleans.Serialization.ProtobufNet/ProtobufTypeCacheItem.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-using ProtoBuf;
-
-namespace Orleans.Serialization.ProtobufNet
-{
- public class ProtobufTypeCacheItem
- {
- public readonly bool IsSupported;
- public readonly bool IsImmutable;
-
- public ProtobufTypeCacheItem(Type type)
- {
- IsSupported = type.IsDefined(typeof(ProtoContractAttribute), false);
- IsImmutable = type.IsDefined(typeof(ImmutableAttribute), false);
- }
- }
-}
diff --git a/test/Orleans.Serialization.UnitTests/Orleans.Serialization.UnitTests.csproj b/test/Orleans.Serialization.UnitTests/Orleans.Serialization.UnitTests.csproj
index 7ef2cde2337..c1bfc52eeac 100644
--- a/test/Orleans.Serialization.UnitTests/Orleans.Serialization.UnitTests.csproj
+++ b/test/Orleans.Serialization.UnitTests/Orleans.Serialization.UnitTests.csproj
@@ -1,4 +1,4 @@
-
+
true
@@ -16,6 +16,8 @@
+
+
@@ -24,6 +26,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/test/Orleans.Serialization.UnitTests/ProtobufSerializerTests.cs b/test/Orleans.Serialization.UnitTests/ProtobufSerializerTests.cs
new file mode 100644
index 00000000000..0ae491fdbac
--- /dev/null
+++ b/test/Orleans.Serialization.UnitTests/ProtobufSerializerTests.cs
@@ -0,0 +1,112 @@
+#nullable enable
+using System;
+using Google.Protobuf;
+using Microsoft.Extensions.DependencyInjection;
+using Orleans.Serialization.Cloning;
+using Orleans.Serialization.Codecs;
+using Orleans.Serialization.Serializers;
+using Orleans.Serialization.TestKit;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Orleans.Serialization.UnitTests;
+
+[Trait("Category", "BVT")]
+public class ProtobufSerializerTests : FieldCodecTester>
+{
+ public ProtobufSerializerTests(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ protected override void Configure(ISerializerBuilder builder)
+ {
+ builder.AddProtobufSerializer();
+ }
+
+ protected override MyProtobufClass? CreateValue() => new() { IntProperty = 30, StringProperty = "hello", SubClass = new MyProtobufClass.Types.SubClass { Id = Guid.NewGuid().ToByteString() } };
+
+ protected override MyProtobufClass?[] TestValues => new MyProtobufClass?[]
+ {
+ null,
+ new () { SubClass = new MyProtobufClass.Types.SubClass { Id = Guid.NewGuid().ToByteString() } },
+ new () { IntProperty = 150, StringProperty = new string('c', 20), SubClass = new MyProtobufClass.Types.SubClass { Id = Guid.NewGuid().ToByteString() } },
+ new () { IntProperty = -150_000, StringProperty = new string('c', 6_000), SubClass = new MyProtobufClass.Types.SubClass { Id = Guid.NewGuid().ToByteString() } },
+ };
+
+ [Fact]
+ public void ProtobufSerializerDeepCopyTyped()
+ {
+ var original = new MyProtobufClass { IntProperty = 30, StringProperty = "hi", SubClass = new MyProtobufClass.Types.SubClass { Id = Guid.NewGuid().ToByteString() } };
+ var copier = ServiceProvider.GetRequiredService>();
+ var result = copier.Copy(original);
+
+ Assert.Equal(original.IntProperty, result.IntProperty);
+ Assert.Equal(original.StringProperty, result.StringProperty);
+ }
+
+ [Fact]
+ public void ProtobufSerializerDeepCopyUntyped()
+ {
+ var original = new MyProtobufClass { IntProperty = 30, StringProperty = "hi", SubClass = new MyProtobufClass.Types.SubClass { Id = Guid.NewGuid().ToByteString() } };
+ var copier = ServiceProvider.GetRequiredService();
+ var result = (MyProtobufClass)copier.Copy((object)original);
+
+ Assert.Equal(original.IntProperty, result.IntProperty);
+ Assert.Equal(original.StringProperty, result.StringProperty);
+ }
+
+ [Fact]
+ public void ProtobufSerializerRoundTripThroughCodec()
+ {
+ var original = new MyProtobufClass { IntProperty = 30, StringProperty = "hi", SubClass = new MyProtobufClass.Types.SubClass { Id = Guid.NewGuid().ToByteString() } };
+ var result = RoundTripThroughCodec(original);
+
+ Assert.Equal(original.IntProperty, result.IntProperty);
+ Assert.Equal(original.StringProperty, result.StringProperty);
+ }
+
+ [Fact]
+ public void ProtobufSerializerRoundTripThroughUntypedSerializer()
+ {
+ var original = new MyProtobufClass { IntProperty = 30, StringProperty = "hi", SubClass = new MyProtobufClass.Types.SubClass { Id = Guid.NewGuid().ToByteString() } };
+ var untypedResult = RoundTripThroughUntypedSerializer(original, out _);
+
+ var result = Assert.IsType(untypedResult);
+ Assert.Equal(original.IntProperty, result.IntProperty);
+ Assert.Equal(original.StringProperty, result.StringProperty);
+ }
+}
+
+[Trait("Category", "BVT")]
+public class ProtobufCodecCopierTests : CopierTester>
+{
+ public ProtobufCodecCopierTests(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ protected override void Configure(ISerializerBuilder builder)
+ {
+ builder.AddProtobufSerializer();
+ }
+ protected override IDeepCopier CreateCopier() => ServiceProvider.GetRequiredService().GetDeepCopier();
+
+ protected override MyProtobufClass? CreateValue() => new MyProtobufClass { IntProperty = 30, StringProperty = "hello", SubClass = new MyProtobufClass.Types.SubClass { Id = Guid.NewGuid().ToByteString() } };
+
+ protected override MyProtobufClass?[] TestValues => new MyProtobufClass?[]
+ {
+ null,
+ new () { SubClass = new MyProtobufClass.Types.SubClass { Id = Guid.NewGuid().ToByteString() } },
+ new () { IntProperty = 150, StringProperty = new string('c', 20), SubClass = new MyProtobufClass.Types.SubClass { Id = Guid.NewGuid().ToByteString() } },
+ new () { IntProperty = -150_000, StringProperty = new string('c', 6_000), SubClass = new MyProtobufClass.Types.SubClass { Id = Guid.NewGuid().ToByteString() } },
+ };
+}
+
+public static class ProtobufGuidExtensions
+{
+ public static ByteString ToByteString(this Guid guid)
+ {
+ Span span = stackalloc byte[16];
+ guid.TryWriteBytes(span);
+ return ByteString.CopyFrom(span);
+ }
+}
diff --git a/test/Orleans.Serialization.UnitTests/protobuf-model.proto b/test/Orleans.Serialization.UnitTests/protobuf-model.proto
new file mode 100644
index 00000000000..3250ed7da8c
--- /dev/null
+++ b/test/Orleans.Serialization.UnitTests/protobuf-model.proto
@@ -0,0 +1,12 @@
+syntax = "proto3";
+option csharp_namespace = "Orleans.Serialization.UnitTests";
+
+message MyProtobufClass {
+ int32 int_property = 1;
+ string string_property = 2;
+ SubClass sub_class = 3;
+
+ message SubClass {
+ bytes id = 1;
+ }
+}