#if !BESTHTTP_DISABLE_SIGNALR_CORE
using BestHTTP.PlatformSupport.Memory;
using BestHTTP.SignalRCore.Messages;
using System;
using System.Collections.Generic;
#if NETFX_CORE || NET_4_6
using System.Reflection;
#endif
namespace BestHTTP.SignalRCore
{
public interface IProtocol
{
string Name { get; }
TransferModes Type { get; }
IEncoder Encoder { get; }
HubConnection Connection { get; set; }
///
/// This function must parse binary representation of the messages into the list of Messages.
///
void ParseMessages(BufferSegment segment, ref List messages);
///
/// This function must return the encoded representation of the given message.
///
BufferSegment EncodeMessage(Message message);
///
/// This function must convert all element in the arguments array to the corresponding type from the argTypes array.
///
object[] GetRealArguments(Type[] argTypes, object[] arguments);
///
/// Convert a value to the given type.
///
object ConvertTo(Type toType, object obj);
}
public sealed class JsonProtocol : IProtocol
{
public const char Separator = (char)0x1E;
public string Name { get { return "json"; } }
public TransferModes Type { get { return TransferModes.Binary; } }
public IEncoder Encoder { get; private set; }
public HubConnection Connection { get; set; }
public JsonProtocol(IEncoder encoder)
{
if (encoder == null)
throw new ArgumentNullException("encoder");
this.Encoder = encoder;
}
public void ParseMessages(BufferSegment segment, ref List messages) {
if (segment.Data == null || segment.Count == 0)
return;
int from = segment.Offset;
int separatorIdx = Array.IndexOf(segment.Data, (byte)JsonProtocol.Separator, from);
if (separatorIdx == -1)
throw new Exception("Missing separator in data! Segment: " + segment.ToString());
while (separatorIdx != -1)
{
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
HTTPManager.Logger.Verbose("JsonProtocol", "ParseMessages - " + System.Text.Encoding.UTF8.GetString(segment.Data, from, separatorIdx - from));
var message = this.Encoder.DecodeAs(new BufferSegment(segment.Data, from, separatorIdx - from));
messages.Add(message);
from = separatorIdx + 1;
separatorIdx = Array.IndexOf(segment.Data, (byte)JsonProtocol.Separator, from);
}
}
public BufferSegment EncodeMessage(Message message)
{
BufferSegment result = BufferSegment.Empty;
// While message contains all informations already, the spec states that no additional field are allowed in messages
// So we are creating 'specialized' messages here to send to the server.
switch (message.type)
{
case MessageTypes.StreamItem:
result = this.Encoder.Encode(new StreamItemMessage()
{
type = message.type,
invocationId = message.invocationId,
item = message.item
});
break;
case MessageTypes.Completion:
if (!string.IsNullOrEmpty(message.error))
{
result = this.Encoder.Encode(new CompletionWithError()
{
type = MessageTypes.Completion,
invocationId = message.invocationId,
error = message.error
});
}
else if (message.result != null)
{
result = this.Encoder.Encode(new CompletionWithResult()
{
type = MessageTypes.Completion,
invocationId = message.invocationId,
result = message.result
});
}
else
result = this.Encoder.Encode(new Completion()
{
type = MessageTypes.Completion,
invocationId = message.invocationId
});
break;
case MessageTypes.Invocation:
case MessageTypes.StreamInvocation:
if (message.streamIds != null)
{
result = this.Encoder.Encode(new UploadInvocationMessage()
{
type = message.type,
invocationId = message.invocationId,
nonblocking = message.nonblocking,
target = message.target,
arguments = message.arguments,
streamIds = message.streamIds
});
}
else
{
result = this.Encoder.Encode(new InvocationMessage()
{
type = message.type,
invocationId = message.invocationId,
nonblocking = message.nonblocking,
target = message.target,
arguments = message.arguments
});
}
break;
case MessageTypes.CancelInvocation:
result = this.Encoder.Encode(new CancelInvocationMessage()
{
invocationId = message.invocationId
});
break;
case MessageTypes.Ping:
//result = this.Encoder.Encode(new PingMessage());
// fast path to encode a well-known json string
result = EncodeKnown("{\"type\":6}");
break;
case MessageTypes.Close:
if (!string.IsNullOrEmpty(message.error))
{
result = this.Encoder.Encode(new CloseWithErrorMessage() { error = message.error });
}
else
{
//result = this.Encoder.Encode(new CloseMessage());
// fast path to encode a well-known json string
result = EncodeKnown("{\"type\":7}");
}
break;
}
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
HTTPManager.Logger.Verbose("JsonProtocol", "EncodeMessage - json: " + System.Text.Encoding.UTF8.GetString(result.Data, 0, result.Count - 1));
return result;
}
private BufferSegment EncodeKnown(string json)
{
int len = System.Text.Encoding.UTF8.GetByteCount(json);
byte[] buffer = BufferPool.Get(len + 1, true);
System.Text.Encoding.UTF8.GetBytes(json, 0, json.Length, buffer, 0);
buffer[len] = (byte)JsonProtocol.Separator;
return new BufferSegment(buffer, 0, len + 1);
}
public object[] GetRealArguments(Type[] argTypes, object[] arguments)
{
if (arguments == null || arguments.Length == 0)
return null;
if (argTypes.Length > arguments.Length)
throw new Exception(string.Format("argType.Length({0}) < arguments.length({1})", argTypes.Length, arguments.Length));
object[] realArgs = new object[arguments.Length];
for (int i = 0; i < arguments.Length; ++i)
realArgs[i] = ConvertTo(argTypes[i], arguments[i]);
return realArgs;
}
public object ConvertTo(Type toType, object obj)
{
if (obj == null)
return null;
#if NETFX_CORE
TypeInfo typeInfo = toType.GetTypeInfo();
#endif
#if NETFX_CORE
if (typeInfo.IsEnum)
#else
if (toType.IsEnum)
#endif
return Enum.Parse(toType, obj.ToString(), true);
#if NETFX_CORE
if (typeInfo.IsPrimitive)
#else
if (toType.IsPrimitive)
#endif
return Convert.ChangeType(obj, toType);
if (toType == typeof(string))
return obj.ToString();
#if NETFX_CORE
if (typeInfo.IsGenericType && toType.Name == "Nullable`1")
return Convert.ChangeType(obj, toType.GenericTypeArguments[0]);
#else
if (toType.IsGenericType && toType.Name == "Nullable`1")
return Convert.ChangeType(obj, toType.GetGenericArguments()[0]);
#endif
return this.Encoder.ConvertTo(toType, obj);
}
///
/// Returns the given string parameter's bytes with the added separator(0x1E).
///
public static BufferSegment WithSeparator(string str)
{
int len = System.Text.Encoding.UTF8.GetByteCount(str);
byte[] buffer = BufferPool.Get(len + 1, true);
System.Text.Encoding.UTF8.GetBytes(str, 0, str.Length, buffer, 0);
buffer[len] = 0x1e;
return new BufferSegment(buffer, 0, len + 1);
}
}
}
#endif