You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
556 lines
18 KiB
556 lines
18 KiB
8 months ago
|
#if !BESTHTTP_DISABLE_SOCKETIO
|
||
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Linq;
|
||
|
using System.Text;
|
||
|
|
||
|
using BestHTTP.PlatformSupport.Memory;
|
||
|
using BestHTTP.SocketIO3.Events;
|
||
|
|
||
|
namespace BestHTTP.SocketIO3.Parsers
|
||
|
{
|
||
|
public sealed class Placeholder
|
||
|
{
|
||
|
public bool _placeholder;
|
||
|
public int num;
|
||
|
}
|
||
|
|
||
|
[PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstructionAttribute]
|
||
|
public sealed class DefaultJsonParser : IParser
|
||
|
{
|
||
|
static DefaultJsonParser()
|
||
|
{
|
||
|
BestHTTP.JSON.LitJson.JsonMapper.RegisterImporter<string, byte[]>(str => Convert.FromBase64String(str));
|
||
|
}
|
||
|
|
||
|
private IncomingPacket PacketWithAttachment = IncomingPacket.Empty;
|
||
|
|
||
|
private int ToInt(char ch)
|
||
|
{
|
||
|
int charValue = Convert.ToInt32(ch);
|
||
|
int num = charValue - '0';
|
||
|
if (num < 0 || num > 9)
|
||
|
return -1;
|
||
|
|
||
|
return num;
|
||
|
}
|
||
|
|
||
|
public IncomingPacket Parse(SocketManager manager, string from)
|
||
|
{
|
||
|
int idx = 0;
|
||
|
var transportEvent = (TransportEventTypes)ToInt(from[idx++]);
|
||
|
var socketIOEvent = SocketIOEventTypes.Unknown;
|
||
|
var nsp = string.Empty;
|
||
|
var id = -1;
|
||
|
var payload = string.Empty;
|
||
|
int attachments = 0;
|
||
|
|
||
|
if (from.Length > idx && ToInt(from[idx]) >= 0)
|
||
|
socketIOEvent = (SocketIOEventTypes)ToInt(from[idx++]);
|
||
|
else
|
||
|
socketIOEvent = SocketIOEventTypes.Unknown;
|
||
|
|
||
|
// Parse Attachment
|
||
|
if (socketIOEvent == SocketIOEventTypes.BinaryEvent || socketIOEvent == SocketIOEventTypes.BinaryAck)
|
||
|
{
|
||
|
int endIdx = from.IndexOf('-', idx);
|
||
|
if (endIdx == -1)
|
||
|
endIdx = from.Length;
|
||
|
|
||
|
int.TryParse(from.Substring(idx, endIdx - idx), out attachments);
|
||
|
|
||
|
idx = endIdx + 1;
|
||
|
}
|
||
|
|
||
|
// Parse Namespace
|
||
|
if (from.Length > idx && from[idx] == '/')
|
||
|
{
|
||
|
int endIdx = from.IndexOf(',', idx);
|
||
|
if (endIdx == -1)
|
||
|
endIdx = from.Length;
|
||
|
|
||
|
nsp = from.Substring(idx, endIdx - idx);
|
||
|
idx = endIdx + 1;
|
||
|
}
|
||
|
else
|
||
|
nsp = "/";
|
||
|
|
||
|
// Parse Id
|
||
|
if (from.Length > idx && ToInt(from[idx]) >= 0)
|
||
|
{
|
||
|
int startIdx = idx++;
|
||
|
while (from.Length > idx && ToInt(from[idx]) >= 0)
|
||
|
idx++;
|
||
|
|
||
|
int.TryParse(from.Substring(startIdx, idx - startIdx), out id);
|
||
|
}
|
||
|
|
||
|
// What left is the payload data
|
||
|
if (from.Length > idx)
|
||
|
payload = from.Substring(idx);
|
||
|
else
|
||
|
payload = string.Empty;
|
||
|
|
||
|
var packet = new IncomingPacket(transportEvent, socketIOEvent, nsp, id);
|
||
|
packet.AttachementCount = attachments;
|
||
|
|
||
|
string eventName = packet.EventName;
|
||
|
object[] args = null;
|
||
|
|
||
|
switch (socketIOEvent)
|
||
|
{
|
||
|
case SocketIOEventTypes.Unknown:
|
||
|
packet.DecodedArg = payload;
|
||
|
break;
|
||
|
|
||
|
case SocketIOEventTypes.Connect:
|
||
|
// No Data | Object
|
||
|
if (!string.IsNullOrEmpty(payload))
|
||
|
(eventName, args) = ReadData(manager, packet, payload);
|
||
|
break;
|
||
|
|
||
|
case SocketIOEventTypes.Disconnect:
|
||
|
// No Data
|
||
|
break;
|
||
|
|
||
|
case SocketIOEventTypes.Error:
|
||
|
// String | Object
|
||
|
(eventName, args) = ReadData(manager, packet, payload);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
// Array
|
||
|
(eventName, args) = ReadData(manager, packet, payload);
|
||
|
// Save payload until all attachments arrive
|
||
|
if (packet.AttachementCount > 0)
|
||
|
packet.DecodedArg = payload;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
packet.EventName = eventName;
|
||
|
|
||
|
if (args != null)
|
||
|
{
|
||
|
if (args.Length == 1)
|
||
|
packet.DecodedArg = args[0];
|
||
|
else
|
||
|
packet.DecodedArgs = args;
|
||
|
}
|
||
|
|
||
|
if (packet.AttachementCount > 0)
|
||
|
{
|
||
|
PacketWithAttachment = packet;
|
||
|
return IncomingPacket.Empty;
|
||
|
}
|
||
|
|
||
|
return packet;
|
||
|
}
|
||
|
|
||
|
public IncomingPacket MergeAttachements(SocketManager manager, IncomingPacket packet)
|
||
|
{
|
||
|
string payload = packet.DecodedArg as string;
|
||
|
packet.DecodedArg = null;
|
||
|
|
||
|
string placeholderFormat = "{{\"_placeholder\":true,\"num\":{0}}}";
|
||
|
|
||
|
for (int i = 0; i < packet.Attachements.Count; ++i)
|
||
|
{
|
||
|
string placeholder = string.Format(placeholderFormat, i);
|
||
|
BufferSegment data = packet.Attachements[i];
|
||
|
|
||
|
payload = payload.Replace(placeholder, "\"" + Convert.ToBase64String(data.Data, data.Offset, data.Count) + "\"");
|
||
|
}
|
||
|
|
||
|
(string eventName, object[] args) = ReadData(manager, packet, payload);
|
||
|
|
||
|
packet.EventName = eventName;
|
||
|
|
||
|
if (args != null)
|
||
|
{
|
||
|
if (args.Length == 1)
|
||
|
packet.DecodedArg = args[0];
|
||
|
else
|
||
|
packet.DecodedArgs = args;
|
||
|
}
|
||
|
|
||
|
return packet;
|
||
|
}
|
||
|
|
||
|
private (string, object[]) ReadData(SocketManager manager, IncomingPacket packet, string payload)
|
||
|
{
|
||
|
Socket socket = manager.GetSocket(packet.Namespace);
|
||
|
|
||
|
string eventName = packet.EventName;
|
||
|
Subscription subscription = socket.GetSubscription(eventName);
|
||
|
|
||
|
object[] args = null;
|
||
|
|
||
|
switch (packet.SocketIOEvent)
|
||
|
{
|
||
|
case SocketIOEventTypes.Unknown:
|
||
|
// TODO: Error?
|
||
|
break;
|
||
|
|
||
|
case SocketIOEventTypes.Connect:
|
||
|
// No Data | Object
|
||
|
using (var strReader = new System.IO.StringReader(payload))
|
||
|
args = ReadParameters(socket, subscription, strReader);
|
||
|
break;
|
||
|
|
||
|
case SocketIOEventTypes.Disconnect:
|
||
|
// No Data
|
||
|
break;
|
||
|
|
||
|
case SocketIOEventTypes.Error:
|
||
|
// String | Object
|
||
|
switch (payload[0])
|
||
|
{
|
||
|
case '{':
|
||
|
using (var strReader = new System.IO.StringReader(payload))
|
||
|
args = ReadParameters(socket, subscription, strReader);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
args = new object[] { new Error(payload) };
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case SocketIOEventTypes.Ack:
|
||
|
eventName = IncomingPacket.GenerateAcknowledgementNameFromId(packet.Id);
|
||
|
subscription = socket.GetSubscription(eventName);
|
||
|
|
||
|
args = ReadParameters(socket, subscription, JSON.LitJson.JsonMapper.ToObject<List<object>>(payload), 0);
|
||
|
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
// Array
|
||
|
|
||
|
List<object> array = null;
|
||
|
using (var reader = new System.IO.StringReader(payload))
|
||
|
array = JSON.LitJson.JsonMapper.ToObject<List<object>>(new JSON.LitJson.JsonReader(reader));
|
||
|
|
||
|
if (array.Count > 0)
|
||
|
{
|
||
|
eventName = array[0].ToString();
|
||
|
subscription = socket.GetSubscription(eventName);
|
||
|
}
|
||
|
|
||
|
if (packet.AttachementCount == 0 || packet.Attachements != null)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
args = ReadParameters(socket, subscription, array, 1);
|
||
|
}
|
||
|
catch(Exception ex)
|
||
|
{
|
||
|
HTTPManager.Logger.Exception("DefaultJsonParser", string.Format("ReadParameters with eventName: {0}", eventName), ex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return (eventName, args);
|
||
|
}
|
||
|
|
||
|
private object[] ReadParameters(Socket socket, Subscription subscription, List<object> array, int startIdx)
|
||
|
{
|
||
|
object[] args = null;
|
||
|
|
||
|
if (array.Count > startIdx)
|
||
|
{
|
||
|
var desc = subscription != null ? subscription.callbacks.FirstOrDefault() : default(CallbackDescriptor);
|
||
|
int paramCount = desc.ParamTypes != null ? desc.ParamTypes.Length : 0;
|
||
|
|
||
|
int arrayIdx = startIdx;
|
||
|
if (paramCount > 0)
|
||
|
{
|
||
|
args = new object[paramCount];
|
||
|
|
||
|
for (int i = 0; i < desc.ParamTypes.Length; ++i)
|
||
|
{
|
||
|
Type type = desc.ParamTypes[i];
|
||
|
|
||
|
if (type == typeof(Socket))
|
||
|
args[i] = socket;
|
||
|
else if (type == typeof(SocketManager))
|
||
|
args[i] = socket.Manager;
|
||
|
else if (type == typeof(Placeholder))
|
||
|
args[i] = new Placeholder();
|
||
|
else
|
||
|
args[i] = ConvertTo(desc.ParamTypes[i], array[arrayIdx++]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return args;
|
||
|
}
|
||
|
|
||
|
public object ConvertTo(Type toType, object obj)
|
||
|
{
|
||
|
if (obj == null)
|
||
|
return null;
|
||
|
|
||
|
#if NETFX_CORE
|
||
|
TypeInfo objType = obj.GetType().GetTypeInfo();
|
||
|
#else
|
||
|
Type objType = obj.GetType();
|
||
|
#endif
|
||
|
|
||
|
#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
|
||
|
|
||
|
#if NETFX_CORE
|
||
|
if (objType.Equals(typeInfo))
|
||
|
#else
|
||
|
if (objType.Equals(toType))
|
||
|
#endif
|
||
|
return obj;
|
||
|
|
||
|
if (toType == typeof(byte[]) && objType == typeof(string))
|
||
|
return Convert.FromBase64String(obj.ToString());
|
||
|
|
||
|
return JSON.LitJson.JsonMapper.ToObject(toType, JSON.LitJson.JsonMapper.ToJson(obj));
|
||
|
}
|
||
|
|
||
|
private object[] ReadParameters(Socket socket, Subscription subscription, System.IO.TextReader reader)
|
||
|
{
|
||
|
var desc = subscription != null ? subscription.callbacks.FirstOrDefault() : default(CallbackDescriptor);
|
||
|
int paramCount = desc.ParamTypes != null ? desc.ParamTypes.Length : 0;
|
||
|
object[] args = null;
|
||
|
|
||
|
if (paramCount > 0)
|
||
|
{
|
||
|
args = new object[paramCount];
|
||
|
|
||
|
for (int i = 0; i < desc.ParamTypes.Length; ++i)
|
||
|
{
|
||
|
Type type = desc.ParamTypes[i];
|
||
|
|
||
|
if (type == typeof(Socket))
|
||
|
args[i] = socket;
|
||
|
else if (type == typeof(SocketManager))
|
||
|
args[i] = socket.Manager;
|
||
|
else {
|
||
|
BestHTTP.JSON.LitJson.JsonReader jr = new JSON.LitJson.JsonReader(reader);
|
||
|
args[i] = JSON.LitJson.JsonMapper.ToObject(desc.ParamTypes[i], jr);
|
||
|
reader.Read();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return args;
|
||
|
}
|
||
|
|
||
|
public IncomingPacket Parse(SocketManager manager, BufferSegment data, TransportEventTypes transportEvent = TransportEventTypes.Unknown)
|
||
|
{
|
||
|
IncomingPacket packet = IncomingPacket.Empty;
|
||
|
|
||
|
if (PacketWithAttachment.Attachements == null)
|
||
|
PacketWithAttachment.Attachements = new List<BufferSegment>(PacketWithAttachment.AttachementCount);
|
||
|
PacketWithAttachment.Attachements.Add(data);
|
||
|
|
||
|
if (PacketWithAttachment.Attachements.Count == PacketWithAttachment.AttachementCount)
|
||
|
{
|
||
|
packet = manager.Parser.MergeAttachements(manager, PacketWithAttachment);
|
||
|
PacketWithAttachment = IncomingPacket.Empty;
|
||
|
}
|
||
|
|
||
|
return packet;
|
||
|
}
|
||
|
|
||
|
public OutgoingPacket CreateOutgoing(TransportEventTypes transportEvent, string payload)
|
||
|
{
|
||
|
return new OutgoingPacket { Payload = "" + (char)('0' + (byte)transportEvent) + payload };
|
||
|
}
|
||
|
|
||
|
private StringBuilder builder = new StringBuilder();
|
||
|
public OutgoingPacket CreateOutgoing(Socket socket, SocketIOEventTypes socketIOEvent, int id, string name, object arg)
|
||
|
{
|
||
|
return CreateOutgoing(socket, socketIOEvent, id, name, arg != null ? new object[] { arg } : null);
|
||
|
}
|
||
|
|
||
|
private int GetBinaryCount(object[] args)
|
||
|
{
|
||
|
if (args == null || args.Length == 0)
|
||
|
return 0;
|
||
|
|
||
|
int count = 0;
|
||
|
for (int i = 0; i < args.Length; ++i)
|
||
|
if (args[i] is byte[])
|
||
|
count++;
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
public OutgoingPacket CreateOutgoing(Socket socket, SocketIOEventTypes socketIOEvent, int id, string name, object[] args)
|
||
|
{
|
||
|
builder.Length = 0;
|
||
|
List<byte[]> attachements = null;
|
||
|
|
||
|
switch(socketIOEvent)
|
||
|
{
|
||
|
case SocketIOEventTypes.Ack:
|
||
|
if (GetBinaryCount(args) > 0)
|
||
|
{
|
||
|
attachements = CreatePlaceholders(args);
|
||
|
socketIOEvent = SocketIOEventTypes.BinaryAck;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case SocketIOEventTypes.Event:
|
||
|
if (GetBinaryCount(args) > 0)
|
||
|
{
|
||
|
attachements = CreatePlaceholders(args);
|
||
|
socketIOEvent = SocketIOEventTypes.BinaryEvent;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
builder.Append(((int)TransportEventTypes.Message).ToString());
|
||
|
builder.Append(((int)socketIOEvent).ToString());
|
||
|
|
||
|
if (socketIOEvent == SocketIOEventTypes.BinaryEvent || socketIOEvent == SocketIOEventTypes.BinaryAck)
|
||
|
{
|
||
|
builder.Append(attachements.Count.ToString());
|
||
|
builder.Append('-');
|
||
|
}
|
||
|
|
||
|
// Add the namespace. If there is any other then the root nsp ("/")
|
||
|
// then we have to add a trailing "," if we have more data.
|
||
|
bool nspAdded = false;
|
||
|
if (socket.Namespace != "/")
|
||
|
{
|
||
|
builder.Append(socket.Namespace);
|
||
|
nspAdded = true;
|
||
|
}
|
||
|
|
||
|
// ack id, if any
|
||
|
if (id >= 0)
|
||
|
{
|
||
|
if (nspAdded)
|
||
|
{
|
||
|
builder.Append(',');
|
||
|
nspAdded = false;
|
||
|
}
|
||
|
|
||
|
builder.Append(id.ToString());
|
||
|
}
|
||
|
|
||
|
// payload
|
||
|
switch (socketIOEvent)
|
||
|
{
|
||
|
case SocketIOEventTypes.Connect:
|
||
|
// No Data | Object
|
||
|
if (args != null && args.Length > 0)
|
||
|
{
|
||
|
if (nspAdded) builder.Append(',');
|
||
|
|
||
|
builder.Append(BestHTTP.JSON.LitJson.JsonMapper.ToJson(args[0]));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case SocketIOEventTypes.Disconnect:
|
||
|
// No Data
|
||
|
break;
|
||
|
|
||
|
case SocketIOEventTypes.Error:
|
||
|
// String | Object
|
||
|
if (args != null && args.Length > 0)
|
||
|
{
|
||
|
if (nspAdded) builder.Append(',');
|
||
|
|
||
|
builder.Append(BestHTTP.JSON.LitJson.JsonMapper.ToJson(args[0]));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case SocketIOEventTypes.Ack:
|
||
|
case SocketIOEventTypes.BinaryAck:
|
||
|
if (nspAdded) builder.Append(',');
|
||
|
|
||
|
if (args != null && args.Length > 0)
|
||
|
{
|
||
|
var argsJson = JSON.LitJson.JsonMapper.ToJson(args);
|
||
|
builder.Append(argsJson);
|
||
|
}
|
||
|
else
|
||
|
builder.Append("[]");
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
if (nspAdded) builder.Append(',');
|
||
|
|
||
|
// Array
|
||
|
builder.Append('[');
|
||
|
if (!string.IsNullOrEmpty(name))
|
||
|
{
|
||
|
builder.Append('\"');
|
||
|
builder.Append(name);
|
||
|
builder.Append('\"');
|
||
|
}
|
||
|
|
||
|
if (args != null && args.Length > 0)
|
||
|
{
|
||
|
builder.Append(',');
|
||
|
var argsJson = JSON.LitJson.JsonMapper.ToJson(args);
|
||
|
builder.Append(argsJson, 1, argsJson.Length - 2);
|
||
|
}
|
||
|
|
||
|
builder.Append(']');
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return new OutgoingPacket { Payload = builder.ToString(), Attachements = attachements };
|
||
|
}
|
||
|
|
||
|
private List<byte[]> CreatePlaceholders(object[] args)
|
||
|
{
|
||
|
List<byte[]> attachements = null;
|
||
|
|
||
|
for (int i = 0; i < args.Length; ++i)
|
||
|
{
|
||
|
var binary = args[i] as byte[];
|
||
|
if (binary != null)
|
||
|
{
|
||
|
if (attachements == null)
|
||
|
attachements = new List<byte[]>();
|
||
|
attachements.Add(binary);
|
||
|
|
||
|
args[i] = new Placeholder { _placeholder = true, num = attachements.Count - 1 };
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return attachements;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif
|