Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serialization with encoding #5

Merged
merged 4 commits into from
Mar 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Src/Client/ClientViewModel/ClientVM.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using AsyncAwaitBestPractices.MVVM;
using System;
using System.Text;
using System.Threading.Tasks;
using TPUM.Shared.Connectivity;
using TPUM.Shared.Model.Core;
using TPUM.Shared.ViewModel;

namespace TPUM.Client.ViewModel
Expand All @@ -25,7 +27,7 @@ private async Task Connect()
{
await Task.CompletedTask;
}
_socket = new Socket(url, _repository);
_socket = new Socket(url, Format.JSON, Encoding.Default, _repository);
await _socket.Start();
OnPropertyChanged(nameof(CanDisconnect));
OnPropertyChanged(nameof(CanConnect));
Expand Down
2 changes: 1 addition & 1 deletion Src/Shared/Connectivity/Connectivity.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Text.Json" Version="4.7.2" />
<PackageReference Include="System.Text.Json" Version="5.0.1" />
</ItemGroup>

<ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions Src/Shared/Connectivity/Core/INetworkNode.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using System;
using System.Threading.Tasks;
using TPUM.Shared.Model.Core;

namespace TPUM.Shared.Connectivity.Core
{
public interface INetworkNode : IDisposable
{
ISerializer<NetworkEntity> Serializer { get; }

Task Start();
void Stop();
void Stop(int delay);
Expand Down
15 changes: 15 additions & 0 deletions Src/Shared/Connectivity/Core/NetworkNode.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using TPUM.Shared.Model;
using TPUM.Shared.Model.Core;
using TPUM.Shared.Model.Formatters;

namespace TPUM.Shared.Connectivity.Core
{
Expand All @@ -11,6 +13,19 @@ public abstract class NetworkNode : Observable<NetworkEntity>, INetworkNode
protected internal readonly int _bufferSize = 1024;
protected internal CancellationTokenSource _cancellationTokenSource;

public ISerializer<NetworkEntity> Serializer { get; }

public NetworkNode() : this (Format.JSON, Encoding.Default) { }

public NetworkNode(Format format) : this(format, Encoding.Default) { }

public NetworkNode(Encoding encoding) : this(Format.JSON, encoding) { }

public NetworkNode(Format format, Encoding encoding)
{
Serializer = new Serializer<NetworkEntity>(encoding, FormatterFactory.CreateFormatter<NetworkEntity>(format));
}

public virtual Task Start()
{
_cancellationTokenSource = new CancellationTokenSource();
Expand Down
2 changes: 1 addition & 1 deletion Src/Shared/Connectivity/HttpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using TPUM.Shared.Model;
using TPUM.Shared.Model.Core;
using TPUM.Shared.Model.Entities;

namespace TPUM.Shared.Connectivity
Expand Down
7 changes: 4 additions & 3 deletions Src/Shared/Connectivity/HttpServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using System.Threading;
using System.Threading.Tasks;
using TPUM.Shared.Connectivity.Core;
using TPUM.Shared.Model;
using TPUM.Shared.Model.Core;
using TPUM.Shared.Model.Entities;

Expand All @@ -26,7 +25,9 @@ public class HttpServer : NetworkNode, IObserver<Entity>
public Uri BaseUri { get; }
public bool IsListening => _httpListener?.IsListening ?? false;

public HttpServer(Uri url, IRepository repository)
public HttpServer(Uri url, IRepository repository) : this(url, repository, Format.JSON, Encoding.UTF8) { }

public HttpServer(Uri url, IRepository repository, Format format, Encoding encoding) : base(format, encoding)
{
_repository = repository;
_dataContextSubscription = _repository.Subscribe(this);
Expand Down Expand Up @@ -151,7 +152,7 @@ private HttpListenerResponse RespondHttp(HttpListenerContext context)

private IEnumerable<(Memory<byte> chunk, bool last)> SplitObjectIntoBufferSizedChunks(NetworkEntity entity)
{
byte[] fullArray = Encoding.UTF8.GetBytes(entity.Serialize());
byte[] fullArray = entity.Serialize(Serializer);
int chunksCount = (int)Math.Ceiling((decimal)fullArray.Length / _bufferSize);
byte[] appendedArray = new byte[chunksCount * _bufferSize];
Memory<byte> memory = appendedArray.AsMemory();
Expand Down
10 changes: 4 additions & 6 deletions Src/Shared/Connectivity/Socket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Threading;
using System.Threading.Tasks;
using TPUM.Shared.Connectivity.Core;
using TPUM.Shared.Model;
using TPUM.Shared.Model.Core;

namespace TPUM.Shared.Connectivity
Expand All @@ -18,9 +17,9 @@ public class Socket : NetworkNode
public Uri ServerUri { get; }
public bool IsConnected => _webSocket?.State == WebSocketState.Open;

public Socket(Uri url) : this(url, null) { }
public Socket(Uri url, Format format, Encoding encoding) : this(url, format, encoding, null) { }

public Socket(Uri url, IRepository repository)
public Socket(Uri url, Format format, Encoding encoding, IRepository repository) : base(format, encoding)
{
_webSocket = new ClientWebSocket();
ServerUri = new Uri($"ws://{url}/connect/");
Expand Down Expand Up @@ -82,10 +81,9 @@ private async Task ReadEntity(ArraySegment<byte> buffer, CancellationToken token
{
receivedBytes.RemoveRange(endIndex, receivedBytes.Count - endIndex);
}
string json = Encoding.UTF8.GetString(receivedBytes.ToArray());
NetworkEntity networkEntity = NetworkEntity.Deserialize(json);
NetworkEntity networkEntity = NetworkEntity.Deserialize(receivedBytes.ToArray(), Serializer);
InvokeEntityChanged(networkEntity);
_repository.AddEntity(networkEntity.Entity);
_repository?.AddEntity(networkEntity.Entity);
}

#endregion
Expand Down
21 changes: 19 additions & 2 deletions Src/Shared/Model/Core/Entity.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
using System;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using YamlDotNet.Serialization;

namespace TPUM.Shared.Model.Core
{
[Guid("ABE70968-E4A3-4349-9CE3-FDCD529F0081")]
public class Entity
[DataContract(Name = "Entity", Namespace = "http://tpum.example.com", IsReference = true)]
public class Entity : IExtensibleDataObject
{
[DataMember]
public int Id { get; set; }

#region Serialization support

[YamlIgnore]
[JsonIgnore]
public ExtensionDataObject ExtensionData { get; set; }
[YamlIgnore]
[JsonExtensionData]
public Dictionary<string, object> JsonExtensionData { get; set; }

#endregion

public override bool Equals(object obj)
{
return obj is Entity entity &&
Expand Down
27 changes: 27 additions & 0 deletions Src/Shared/Model/Core/EntityComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;

namespace TPUM.Shared.Model.Core
{
class EntityComparer : IEqualityComparer<Entity>, IComparer<Entity>
{
public int Compare(Entity x, Entity y)
{
Type xType = x.GetType(), yType = y.GetType();
return xType.Equals(yType)
? x.Id - y.Id
: xType.GUID.CompareTo(yType.GUID);
}

public bool Equals(Entity x, Entity y)
{
return x.GetType().Equals(y.GetType())
&& x.Id == y.Id;
}

public int GetHashCode(Entity obj)
{
return 84681463 + 36794213 * obj.Id.GetHashCode();
}
}
}
7 changes: 7 additions & 0 deletions Src/Shared/Model/Core/Format.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace TPUM.Shared.Model.Core
{
public enum Format
{
JSON, YAML, XML
}
}
9 changes: 9 additions & 0 deletions Src/Shared/Model/Core/IFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace TPUM.Shared.Model.Core
{
public interface IFormatter<T>
{
Format Format { get; }
string FormatObject(T obj);
T Deformat(string str);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using TPUM.Shared.Model.Core;
using TPUM.Shared.Model.Entities;

namespace TPUM.Shared.Model
namespace TPUM.Shared.Model.Core
{
public interface IRepository : IObservable<Entity>, IDisposable
{
Expand Down
13 changes: 13 additions & 0 deletions Src/Shared/Model/Core/ISerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Text;

namespace TPUM.Shared.Model.Core
{
public interface ISerializer<T>
{
Encoding Encoding { get; }
IFormatter<T> Formatter { get; }

byte[] Serialize(T obj);
T Deserialize(byte[] bytes);
}
}
93 changes: 70 additions & 23 deletions Src/Shared/Model/Core/NetworkEntity.cs
Original file line number Diff line number Diff line change
@@ -1,42 +1,62 @@
using System;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Runtime.Serialization;
using System.Text;
using YamlDotNet.Serialization;

namespace TPUM.Shared.Model.Core
{
public class NetworkEntity
[DataContract]
public class NetworkEntity : IExtensibleDataObject
{
private const byte ENTITY_ASCII_SEPARATOR = 0x1E;

#region Data properties

[DataMember]
[YamlMember(typeof(string))]
public Uri Source { get; set; }
[DataMember]
public Guid TypeIdentifier { get; set; }
[JsonIgnore]
[YamlIgnore]
public object Entity { get; set; }

public string Serialize()
{
string json = JsonSerializer.Serialize(this);
string value = JsonSerializer.Serialize(Entity);
return $"{json.Substring(0, json.Length - 1)},\"{nameof(Entity)}\":{value}}}";
}
#endregion

#region Serialization support

public static NetworkEntity Deserialize(string sourceJson)
[JsonIgnore]
[YamlIgnore]
public ExtensionDataObject ExtensionData { get; set; }
[YamlIgnore]
[JsonExtensionData]
public Dictionary<string, object> JsonExtensionData { get; set; }

#endregion

public byte[] Serialize(ISerializer<NetworkEntity> serializer)
{
JsonElement root = JsonDocument.Parse(sourceJson).RootElement;
NetworkEntity entity = new NetworkEntity()
{
Source = new Uri(root.GetProperty(nameof(Source)).GetString()),
TypeIdentifier = Guid.Parse(root.GetProperty(nameof(TypeIdentifier)).GetString())
};
return entity.DeserializeValue(sourceJson);
ISerializer<Entity> entitySerializer = new Serializer<Entity>(serializer.Encoding, serializer.Formatter.Format);
byte[] entityBytes = entitySerializer.Serialize(Entity as Entity);
byte[] networkEntityBytes = serializer.Serialize(this);
return networkEntityBytes.Concat(GetEntitySeparator(serializer)).Concat(entityBytes).ToArray();
}

private NetworkEntity DeserializeValue(string sourceJson)
public static NetworkEntity Deserialize(byte[] source, ISerializer<NetworkEntity> serializer)
{
Type type = GetType().Assembly.ExportedTypes.FirstOrDefault(t => t.GUID == TypeIdentifier);
JsonElement jsonValue = JsonDocument.Parse(sourceJson).RootElement.GetProperty(nameof(Entity));
Entity = JsonSerializer.Deserialize(jsonValue.GetRawText(), type);
return this;
ISerializer<Entity> entitySerializer = new Serializer<Entity>(serializer.Encoding, serializer.Formatter.Format);
(int separatorStartIndex, int separatorEndIndex) = GetEntitySeparatorRange(source, serializer);
Span<byte> entityBytes = source.AsSpan(separatorEndIndex, source.Length - separatorEndIndex);
Span<byte> networkEntityBytes = source.AsSpan(0, separatorStartIndex == -1 ? source.Length : separatorStartIndex);
NetworkEntity entity = serializer.Deserialize(networkEntityBytes.ToArray());
if (separatorStartIndex != -1)
{
entity.Entity = entitySerializer.Deserialize(entityBytes.ToArray());
}
return entity;
}

public override bool Equals(object obj)
Expand All @@ -55,5 +75,32 @@ public override int GetHashCode()
hashCode = hashCode * -1521134295 + EqualityComparer<object>.Default.GetHashCode(Entity);
return hashCode;
}

private static (int Start, int End) GetEntitySeparatorRange(byte[] source, ISerializer<NetworkEntity> serializer)
{
byte[] separator = GetEntitySeparator(serializer);
int length = source.Length - separator.Length;
for (int i = 0; i < length; ++i)
{
bool found = true;
for (int j = 0; j < separator.Length && found; ++j)
{
if (source[i + j] != separator[j])
{
found = false;
}
}
if (found)
{
return (i, i + separator.Length);
}
}
return (-1, -1);
}

private static byte[] GetEntitySeparator(ISerializer<NetworkEntity> serializer)
{
return Encoding.Convert(Encoding.ASCII, serializer.Encoding, new[] { ENTITY_ASCII_SEPARATOR });
}
}
}
9 changes: 8 additions & 1 deletion Src/Shared/Model/Entities/Author.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using TPUM.Shared.Model.Core;

namespace TPUM.Shared.Model.Entities
{
[Guid("EB2E9E9F-C340-4244-BC2D-4B9531AEF2DA")]
[DataContract(IsReference = true)]
public class Author : Entity
{
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string LastName { get; set; }
[DataMember]
public string NickName { get; set; }
[DataMember]
public List<Book> Books { get; set; } = new List<Book>();

public override bool Equals(object obj)
Expand All @@ -21,7 +28,7 @@ public override bool Equals(object obj)
FirstName == author.FirstName &&
LastName == author.LastName &&
NickName == author.NickName &&
EqualityComparer<List<Book>>.Default.Equals(Books, author.Books);
Enumerable.SequenceEqual(Books, author.Books, new EntityComparer());
}

public override int GetHashCode()
Expand Down
Loading