/*
Copyright (c) 2017 Ahmed Kh. Zamil
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Text;
using Esiur.Misc;
using System.ComponentModel;
using Esiur.Data;
using Esiur.Core;
using Esiur.Net.IIP;
using Esiur.Resource;
using System.Linq;
using System.Reflection;
using Esiur.Resource.Template;
using System.Runtime.CompilerServices;
using System.Collections;
using System.Dynamic;
namespace Esiur.Data
{
public static class Codec
{
///
/// Check if a DataType is an array
///
/// DataType to check
/// True if DataType is an array, otherwise false
public static bool IsArray(this DataType type)
{
return (((byte)type & 0x80) == 0x80) && (type != DataType.NotModified);
}
///
/// Get the element DataType
///
///
/// Passing UInt8Array will return UInt8
///
/// DataType to get its element DataType
public static DataType GetElementType(this DataType type)
{
return (DataType)((byte)type & 0x7F);
}
///
/// Get DataType array of a given Structure
///
/// Structure to get its DataTypes
/// Distributed connection is required in case a type is at the other end
private static DataType[] GetStructureDateTypes(Structure structure, DistributedConnection connection)
{
var keys = structure.GetKeys();
var types = new DataType[keys.Length];
for (var i = 0; i < keys.Length; i++)
{
types[i] = Codec.GetDataType(structure[keys[i]], connection).type;
}
return types;
}
///
/// Compare two records
///
/// Initial record to compare with
/// Next record to compare with the initial
/// DistributedConnection is required in case a structure holds items at the other end
public static RecordComparisonResult Compare(IRecord initial, IRecord next)
{
if (next == null)
return RecordComparisonResult.Null;
if (initial == null)
return RecordComparisonResult.Record;
if (next == initial)
return RecordComparisonResult.Same;
if (next.GetType() == initial.GetType())
return RecordComparisonResult.RecordSameType;
return RecordComparisonResult.Record;
}
///
/// Compare two structures
///
/// Initial structure to compare with
/// Next structure to compare with the initial
/// DistributedConnection is required in case a structure holds items at the other end
public static StructureComparisonResult Compare(Structure initial, Structure next, DistributedConnection connection)
{
if (next == null)
return StructureComparisonResult.Null;
if (initial == null)
return StructureComparisonResult.Structure;
if (next == initial)
return StructureComparisonResult.Same;
if (initial.Length != next.Length)
return StructureComparisonResult.Structure;
var previousKeys = initial.GetKeys();
var nextKeys = next.GetKeys();
for (var i = 0; i < previousKeys.Length; i++)
if (previousKeys[i] != nextKeys[i])
return StructureComparisonResult.Structure;
var previousTypes = GetStructureDateTypes(initial, connection);
var nextTypes = GetStructureDateTypes(next, connection);
for (var i = 0; i < previousTypes.Length; i++)
if (previousTypes[i] != nextTypes[i])
return StructureComparisonResult.StructureSameKeys;
return StructureComparisonResult.StructureSameTypes;
}
///
/// Compose an array of structures into an array of bytes
///
/// Array of Structure to compose
/// DistributedConnection is required in case a structure in the array holds items at the other end
/// If true, prepend the length as UInt32 at the beginning of the returned bytes array
/// Array of bytes in the network byte order
public static byte[] ComposeStructureArray(Structure[] structures, DistributedConnection connection, bool prependLength = false)
{
if (structures == null || structures?.Length == 0)
return prependLength ? new byte[] { 0, 0, 0, 0 } : new byte[0];
var rt = new BinaryList();
var comparsion = StructureComparisonResult.Structure;
rt.AddUInt8((byte)comparsion)
.AddUInt8Array(ComposeStructure(structures[0], connection, true, true, true));
for (var i = 1; i < structures.Length; i++)
{
comparsion = Compare(structures[i - 1], structures[i], connection);
rt.AddUInt8((byte)comparsion);
if (comparsion == StructureComparisonResult.Structure)
rt.AddUInt8Array(ComposeStructure(structures[i], connection, true, true, true));
else if (comparsion == StructureComparisonResult.StructureSameKeys)
rt.AddUInt8Array(ComposeStructure(structures[i], connection, false, true, true));
else if (comparsion == StructureComparisonResult.StructureSameTypes)
rt.AddUInt8Array(ComposeStructure(structures[i], connection, false, false, true));
}
if (prependLength)
rt.InsertInt32(0, rt.Length);
return rt.ToArray();
}
///
/// Parse an array of structures
///
/// Bytes array
/// Zero-indexed offset
/// Number of bytes to parse
/// DistributedConnection is required in case a structure in the array holds items at the other end
/// Array of structures
public static AsyncBag ParseStructureArray(byte[] data, uint offset, uint length, DistributedConnection connection)
{
var reply = new AsyncBag();
if (length == 0)
{
reply.Seal();
return reply;
}
var end = offset + length;
var result = (StructureComparisonResult)data[offset++];
AsyncReply previous = null;
// string[] previousKeys = null;
// DataType[] previousTypes = null;
Structure.StructureMetadata metadata = new Structure.StructureMetadata();
if (result == StructureComparisonResult.Null)
previous = new AsyncReply(null);
else if (result == StructureComparisonResult.Structure)
{
uint cs = data.GetUInt32(offset);
offset += 4;
previous = ParseStructure(data, offset, cs, connection, out metadata);
offset += cs;
}
reply.Add(previous);
while (offset < end)
{
result = (StructureComparisonResult)data[offset++];
if (result == StructureComparisonResult.Null)
previous = new AsyncReply(null);
else if (result == StructureComparisonResult.Structure)
{
uint cs = data.GetUInt32(offset);
offset += 4;
previous = ParseStructure(data, offset, cs, connection, out metadata);// out previousKeys, out previousTypes);
offset += cs;
}
else if (result == StructureComparisonResult.StructureSameKeys)
{
uint cs = data.GetUInt32(offset);
offset += 4;
previous = ParseStructure(data, offset, cs, connection, out metadata, metadata.Keys);
offset += cs;
}
else if (result == StructureComparisonResult.StructureSameTypes)
{
uint cs = data.GetUInt32(offset);
offset += 4;
previous = ParseStructure(data, offset, cs, connection, out metadata, metadata.Keys, metadata.Types);
offset += cs;
}
reply.Add(previous);
}
reply.Seal();
return reply;
}
public static AsyncBag ParseRecordArray(byte[] data, uint offset, uint length, DistributedConnection connection)
{
var reply = new AsyncBag();
if (length == 0)
{
reply.Seal();
return reply;
}
var end = offset + length;
var result = (RecordComparisonResult)data[offset++];
AsyncReply previous = null;
Guid? classId = null;
if (result == RecordComparisonResult.Null)
previous = new AsyncReply(null);
else if (result == RecordComparisonResult.Record)
{
uint cs = data.GetUInt32(offset);
uint recordLength = cs - 16;
offset += 4;
classId = data.GetGuid(offset);
offset += 16;
previous = ParseRecord(data, offset, recordLength, connection, classId);
offset += recordLength;
}
reply.Add(previous);
while (offset < end)
{
result = (RecordComparisonResult)data[offset++];
if (result == RecordComparisonResult.Null)
previous = new AsyncReply(null);
else if (result == RecordComparisonResult.Record)
{
uint cs = data.GetUInt32(offset);
uint recordLength = cs - 16;
offset += 4;
classId = data.GetGuid(offset);
offset += 16;
previous = ParseRecord(data, offset, recordLength, connection, classId);
offset += recordLength;
}
else if (result == RecordComparisonResult.RecordSameType)
{
uint cs = data.GetUInt32(offset);
offset += 4;
previous = ParseRecord(data, offset, cs, connection, classId);
offset += cs;
}
else if (result == RecordComparisonResult.Same)
{
// do nothing
}
reply.Add(previous);
}
reply.Seal();
return reply;
}
public static AsyncReply ParseRecord(byte[] data, uint offset, uint length, DistributedConnection connection, Guid? classId = null)
{
var reply = new AsyncReply();
if (classId == null)
{
classId = data.GetGuid(offset);
offset += 16;
length -= 16;
}
var template = Warehouse.GetTemplateByClassId((Guid)classId);
if (template != null)
{
ParseVarArray(data, offset, length, connection).Then(ar =>
{
if (template.ResourceType != null)
{
var record = Activator.CreateInstance(template.ResourceType) as IRecord;
for (var i = 0; i < template.Properties.Length; i++)
template.Properties[i].PropertyInfo.SetValue(record, ar[i]);
reply.Trigger(record);
}
else
{
var record = new Record();
for (var i = 0; i < template.Properties.Length; i++)
record.Add(template.Properties[i].Name, ar[i]);
reply.Trigger(record);
}
});
}
else
{
connection.GetTemplate((Guid)classId).Then(tmp => {
ParseVarArray(data, offset, length, connection).Then(ar =>
{
var record = new Record();
for (var i = 0; i < tmp.Properties.Length; i++)
record.Add(tmp.Properties[i].Name, ar[i]);
reply.Trigger(record);
});
}).Error(x=>reply.TriggerError(x));
}
return reply;
}
public static byte[] ComposeRecord(IRecord record, DistributedConnection connection, bool includeClassId = true, bool prependLength = false)
{
var rt = new BinaryList();
var template = Warehouse.GetTemplateByType(record.GetType());
if (includeClassId)
rt.AddGuid(template.ClassId);
foreach (var pt in template.Properties)
{
var value = pt.PropertyInfo.GetValue(record, null);
rt.AddUInt8Array(Compose(value, connection));
}
if (prependLength)
rt.InsertInt32(0, rt.Length);
return rt.ToArray();
}
public static byte[] ComposeRecordArray(IRecord[] records, DistributedConnection connection, bool prependLength = false)
{
if (records == null || records?.Length == 0)
return prependLength ? new byte[] { 0, 0, 0, 0 } : new byte[0];
var rt = new BinaryList();
var comparsion = Compare(null, records[0]);
rt.AddUInt8((byte)comparsion);
if (comparsion == RecordComparisonResult.Record)
rt.AddUInt8Array(ComposeRecord(records[0], connection, true, true));
for (var i = 1; i < records.Length; i++)
{
comparsion = Compare(records[i - 1], records[i]);
rt.AddUInt8((byte)comparsion);
if (comparsion == RecordComparisonResult.Record)
rt.AddUInt8Array(ComposeRecord(records[i], connection, true, true));
else if (comparsion == RecordComparisonResult.RecordSameType)
rt.AddUInt8Array(ComposeRecord(records[i], connection, false, true));
}
if (prependLength)
rt.InsertInt32(0, rt.Length);
return rt.ToArray();
}
///
/// Compose a structure into an array of bytes
///
/// Structure to compose
/// DistributedConnection is required in case an item in the structure is at the other end
/// Whether to include the structure keys
/// Whether to include each item DataType
/// If true, prepend the length as UInt32 at the beginning of the returned bytes array
/// Array of bytes in the network byte order
public static byte[] ComposeStructure(Structure value, DistributedConnection connection,
bool includeKeys = true, bool includeTypes = true, bool prependLength = false)
{
var rt = new BinaryList();
if (includeKeys)
{
foreach (var i in value)
{
var key = DC.ToBytes(i.Key);
rt.AddUInt8((byte)key.Length)
.AddUInt8Array(key)
.AddUInt8Array(Compose(i.Value, connection));
}
}
else
{
foreach (var i in value)
rt.AddUInt8Array(Compose(i.Value, connection, includeTypes));
}
if (prependLength)
rt.InsertInt32(0, rt.Length);
return rt.ToArray();
}
///
/// Parse a structure
///
/// Bytes array
/// Zero-indexed offset.
/// Number of bytes to parse.
/// DistributedConnection is required in case a structure in the array holds items at the other end.
/// Value
public static AsyncReply ParseStructure(byte[] data, uint offset, uint contentLength, DistributedConnection connection)
{
Structure.StructureMetadata metadata;
return ParseStructure(data, offset, contentLength, connection, out metadata);
}
///
/// Parse a structure
///
/// Bytes array
/// Zero-indexed offset.
/// Number of bytes to parse.
/// DistributedConnection is required in case a structure in the array holds items at the other end.
/// Array to store keys in.
/// Array to store DataTypes in.
/// Array of keys, in case the data doesn't include keys
/// Array of DataTypes, in case the data doesn't include DataTypes
/// Structure
public static AsyncReply ParseStructure(byte[] data, uint offset, uint length, DistributedConnection connection, out Structure.StructureMetadata metadata, string[] keys = null, DataType[] types = null)// out string[] parsedKeys, out DataType[] parsedTypes, string[] keys = null, DataType[] types = null)
{
var reply = new AsyncReply();
var bag = new AsyncBag