using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Buttplug.Core;
using Buttplug.Core.Messages;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: InternalsVisibleTo("Buttplug.Test")]
[assembly: InternalsVisibleTo("Buttplug.Client.Test")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Nonpolynomial Labs, LLC")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright Nonpolynomial Labs, LLC")]
[assembly: AssemblyDescription("Buttplug Sex Toy Control Library. Contains Core (messages, errors, etc), Client, and Websocket Connector components")]
[assembly: AssemblyFileVersion("4.0.0.0")]
[assembly: AssemblyInformationalVersion("4.0.0+41a6e2363781583c9c9f475b1544b7ebee02bd52")]
[assembly: AssemblyProduct("Buttplug")]
[assembly: AssemblyTitle("Buttplug")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/buttplugio/buttplug-csharp")]
[assembly: AssemblyVersion("4.0.0.0")]
namespace Buttplug.Core
{
public static class ButtplugConsts
{
public const uint SystemMsgId = 0u;
public const uint DefaultMsgId = 1u;
public const uint CurrentSpecVersion = 3u;
}
public class ButtplugDeviceException : ButtplugException
{
public ButtplugDeviceException(string message, uint id = 0u, Exception inner = null)
: base(message, Error.ErrorClass.ERROR_DEVICE, id, inner)
{
}
}
public class ButtplugException : Exception
{
public Error ButtplugErrorMessage { get; }
public static ButtplugException FromError(Error msg)
{
return msg.ErrorCode switch
{
Error.ErrorClass.ERROR_DEVICE => new ButtplugDeviceException(msg.ErrorMessage, msg.Id),
Error.ErrorClass.ERROR_INIT => new ButtplugHandshakeException(msg.ErrorMessage, msg.Id),
Error.ErrorClass.ERROR_MSG => new ButtplugMessageException(msg.ErrorMessage, msg.Id),
Error.ErrorClass.ERROR_PING => new ButtplugPingException(msg.ErrorMessage, msg.Id),
Error.ErrorClass.ERROR_UNKNOWN => new ButtplugException(msg.ErrorMessage, msg.Id),
_ => new ButtplugException(msg.ErrorMessage, msg.Id),
};
}
public ButtplugException(string message, uint id = 0u, Exception inner = null)
: this(message, Error.ErrorClass.ERROR_UNKNOWN, id, inner)
{
}
public ButtplugException(string message, Error.ErrorClass err = Error.ErrorClass.ERROR_UNKNOWN, uint id = 0u, Exception inner = null)
: base(message, inner)
{
ButtplugErrorMessage = new Error(message, err, id);
}
}
public class ButtplugExceptionEventArgs : EventArgs
{
public ButtplugException Exception { get; }
public ButtplugExceptionEventArgs(ButtplugException ex)
{
Exception = ex;
}
}
public class ButtplugHandshakeException : ButtplugException
{
public ButtplugHandshakeException(string message, uint id = 0u, Exception inner = null)
: base(message, Error.ErrorClass.ERROR_INIT, id, inner)
{
}
}
public class ButtplugJsonMessageParser
{
private readonly Dictionary<string, Type> _messageTypes;
private readonly JsonSerializer _serializer;
public ButtplugJsonMessageParser()
{
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Expected O, but got Unknown
_serializer = new JsonSerializer
{
MissingMemberHandling = (MissingMemberHandling)1
};
_messageTypes = new Dictionary<string, Type>();
foreach (Type allMessageType in ButtplugUtils.GetAllMessageTypes())
{
_messageTypes.Add(allMessageType.Name, allMessageType);
}
if (!_messageTypes.Any())
{
throw new ButtplugMessageException("No message types available.");
}
}
public IEnumerable<ButtplugMessage> Deserialize(string jsonMsg)
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Expected O, but got Unknown
//IL_0031: Expected O, but got Unknown
//IL_005b: Expected O, but got Unknown
//IL_007b: Unknown result type (might be due to invalid IL or missing references)
//IL_0080: Unknown result type (might be due to invalid IL or missing references)
JsonTextReader val = new JsonTextReader((TextReader)new StringReader(jsonMsg))
{
CloseInput = false,
SupportMultipleContent = true
};
List<ButtplugMessage> list = new List<ButtplugMessage>();
while (true)
{
try
{
if (!((JsonReader)val).Read())
{
return list;
}
}
catch (JsonReaderException val2)
{
JsonReaderException val3 = val2;
throw new ButtplugMessageException("Not valid JSON: " + jsonMsg + " - " + ((Exception)(object)val3).Message);
}
JArray val4;
try
{
val4 = JArray.Load((JsonReader)(object)val);
}
catch (JsonReaderException val5)
{
JsonReaderException val6 = val5;
throw new ButtplugMessageException("Not valid JSON: " + jsonMsg + " - " + ((Exception)(object)val6).Message);
}
foreach (JObject item in ((JToken)val4).Children<JObject>())
{
string name = item.Properties().First().Name;
if (!_messageTypes.ContainsKey(name))
{
throw new ButtplugMessageException(name + " is not a valid message class");
}
list.Add(DeserializeAs(item, _messageTypes[name]));
}
}
}
private ButtplugMessage DeserializeAs(JObject obj, Type msgType)
{
//IL_00c0: Expected O, but got Unknown
if (!msgType.IsSubclassOf(typeof(ButtplugMessage)))
{
throw new ButtplugMessageException("Type " + msgType.Name + " is not a subclass of ButtplugMessage");
}
if (msgType.Namespace != "Buttplug.Core.Messages")
{
throw new ButtplugMessageException("Type " + msgType.Name + " (" + msgType.Namespace + ") is not in the namespace of Buttplug.Core.Messages");
}
string name = ButtplugMessage.GetName(msgType);
try
{
return (ButtplugMessage)((JToken)Extensions.Value<JObject>((IEnumerable<JToken>)obj[name])).ToObject(msgType, _serializer);
}
catch (InvalidCastException ex)
{
throw new ButtplugMessageException($"Could not create message for JSON {obj}: {ex.Message}");
}
catch (JsonSerializationException val)
{
JsonSerializationException val2 = val;
throw new ButtplugMessageException($"Could not create message for JSON {obj}: {((Exception)(object)val2).Message}");
}
}
public string Serialize(ButtplugMessage msg)
{
//IL_007b: Unknown result type (might be due to invalid IL or missing references)
//IL_0080: Unknown result type (might be due to invalid IL or missing references)
if (msg.GetType().Namespace != "Buttplug.Core.Messages")
{
throw new ButtplugMessageException("Type " + msg.GetType().Name + " (" + msg.GetType().Namespace + ") is not in the namespace of Buttplug.Core.Messages");
}
JObject val = ButtplugMessageToJObject(msg);
if (val == null)
{
throw new ButtplugMessageException("Message cannot be converted to JSON.", msg.Id);
}
JArray val2 = new JArray();
val2.Add((JToken)(object)val);
return ((JToken)val2).ToString((Formatting)0, Array.Empty<JsonConverter>());
}
public string Serialize(IEnumerable<ButtplugMessage> msgs)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Expected O, but got Unknown
JArray val = new JArray();
foreach (ButtplugMessage msg in msgs)
{
JObject val2 = ButtplugMessageToJObject(msg);
if (val2 != null)
{
val.Add((JToken)(object)val2);
}
}
if (!((IEnumerable<JToken>)val).Any())
{
throw new ButtplugMessageException("No messages serialized.");
}
return ((JToken)val).ToString((Formatting)0, Array.Empty<JsonConverter>());
}
private JObject ButtplugMessageToJObject(ButtplugMessage msg)
{
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
//IL_0016: Expected O, but got Unknown
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
//IL_0017: Expected O, but got Unknown
return new JObject((object)new JProperty(msg.Name, (object)JObject.FromObject((object)msg)));
}
}
public class ButtplugMessageException : ButtplugException
{
public ButtplugMessageException(string message, uint id = 0u, Exception inner = null)
: base(message, Error.ErrorClass.ERROR_MSG, id, inner)
{
}
}
public class ButtplugPingException : ButtplugException
{
public ButtplugPingException(string message, uint id = 0u, Exception inner = null)
: base(message, Error.ErrorClass.ERROR_PING, id, inner)
{
}
}
public static class ButtplugUtils
{
public static IEnumerable<Type> GetAllMessageTypes()
{
IEnumerable<Type> enumerable;
try
{
enumerable = Assembly.GetAssembly(typeof(ButtplugMessage))?.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
enumerable = ex.Types;
}
return (enumerable ?? throw new InvalidOperationException()).Where((Type type) => type != null && type.IsClass && type.IsSubclassOf(typeof(ButtplugMessage)) && type != typeof(ButtplugDeviceMessage));
}
[DebuggerStepThrough]
public static void ArgumentNotNull(object argument, string argumentName)
{
if (argument == null)
{
throw new ArgumentNullException(argumentName);
}
}
public static Type GetMessageType(string messageName)
{
return Type.GetType("Buttplug.Core.Messages." + messageName);
}
}
}
namespace Buttplug.Core.Messages
{
public class ButtplugDeviceMessage : ButtplugMessage
{
[JsonProperty(/*Could not decode attribute arguments.*/)]
public uint DeviceIndex { get; set; }
public ButtplugDeviceMessage(uint id = 1u, uint deviceIndex = uint.MaxValue)
: base(id)
{
DeviceIndex = deviceIndex;
}
}
public abstract class ButtplugMessage
{
private static readonly Dictionary<Type, ButtplugMessageMetadata> _metadataCache = new Dictionary<Type, ButtplugMessageMetadata>();
[JsonProperty(/*Could not decode attribute arguments.*/)]
public uint Id { get; set; }
[JsonIgnore]
public string Name => GetName(GetType());
protected ButtplugMessage(uint id)
{
Id = id;
}
private static T GetMessageAttribute<T>(Type msgType, Func<ButtplugMessageMetadata, T> func)
{
ButtplugUtils.ArgumentNotNull(msgType, "msgType");
ButtplugUtils.ArgumentNotNull(func, "func");
if (!msgType.IsSubclassOf(typeof(ButtplugMessage)))
{
throw new ArgumentException("Argument " + msgType.Name + " must be a subclass of ButtplugMessage");
}
if (_metadataCache.ContainsKey(msgType))
{
return func(_metadataCache[msgType]);
}
Attribute[] customAttributes = Attribute.GetCustomAttributes(msgType);
for (int i = 0; i < customAttributes.Length; i++)
{
if (customAttributes[i] is ButtplugMessageMetadata buttplugMessageMetadata)
{
_metadataCache[msgType] = buttplugMessageMetadata;
return func(buttplugMessageMetadata);
}
}
throw new ArgumentException($"Type {msgType} does not have ButtplugMessageMetadata Attributes");
}
public static string GetName(Type msgType)
{
return GetMessageAttribute(msgType, (ButtplugMessageMetadata md) => md.Name);
}
}
public interface IButtplugMessageOutgoingOnly
{
}
public interface IButtplugDeviceInfoMessage
{
string DeviceName { get; }
uint DeviceIndex { get; }
DeviceMessageAttributes DeviceMessages { get; }
string DeviceDisplayName { get; }
uint DeviceMessageTimingGap { get; }
}
[AttributeUsage(AttributeTargets.Class)]
public class ButtplugMessageMetadata : Attribute
{
public string Name { get; }
public ButtplugMessageMetadata(string name)
{
Name = name;
}
}
[JsonConverter(typeof(StringEnumConverter))]
public enum ActuatorType
{
[EnumMember(Value = "Unknown")]
Unknown,
[EnumMember(Value = "Vibrate")]
Vibrate,
[EnumMember(Value = "Rotate")]
Rotate,
[EnumMember(Value = "Oscillate")]
Oscillate,
[EnumMember(Value = "Constrict")]
Constrict,
[EnumMember(Value = "Inflate")]
Inflate,
[EnumMember(Value = "Position")]
Position
}
[JsonConverter(typeof(StringEnumConverter))]
public enum SensorType
{
[EnumMember(Value = "Unknown")]
Unknown,
[EnumMember(Value = "Battery")]
Battery,
[EnumMember(Value = "RSSI")]
RSSI,
[EnumMember(Value = "Button")]
Button,
[EnumMember(Value = "Pressure")]
Pressure
}
public class GenericDeviceMessageAttributes
{
[JsonIgnore]
internal uint _index;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public readonly string FeatureDescriptor;
[JsonProperty(/*Could not decode attribute arguments.*/)]
[JsonConverter(typeof(StringEnumConverter))]
public readonly ActuatorType ActuatorType;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public readonly uint StepCount;
[JsonIgnore]
public uint Index => _index;
}
public class SensorDeviceMessageAttributes
{
[JsonIgnore]
internal uint _index;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public readonly string FeatureDescriptor;
[JsonProperty(/*Could not decode attribute arguments.*/)]
[JsonConverter(typeof(StringEnumConverter))]
public readonly SensorType SensorType;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public readonly uint[][] SensorRange;
[JsonIgnore]
public uint Index => _index;
}
public class RawDeviceMessageAttributes
{
public readonly string[] Endpoints;
}
public class NullDeviceMessageAttributes
{
}
public class DeviceMessageAttributes
{
internal class EnumeratePair<T>
{
public readonly int index;
public readonly T attr;
public EnumeratePair(T attr, int index)
{
this.index = index;
this.attr = attr;
}
}
public GenericDeviceMessageAttributes[] ScalarCmd;
public GenericDeviceMessageAttributes[] RotateCmd;
public GenericDeviceMessageAttributes[] LinearCmd;
public SensorDeviceMessageAttributes[] SensorReadCmd;
public SensorDeviceMessageAttributes[] SensorSubscribeCmd;
public readonly RawDeviceMessageAttributes[] RawReadCmd;
public readonly RawDeviceMessageAttributes[] RawWriteCmd;
public readonly RawDeviceMessageAttributes[] RawSubscribeCmd;
public readonly NullDeviceMessageAttributes StopDeviceCmd;
[OnDeserialized]
internal void OnDeserializedMethod(StreamingContext context)
{
ScalarCmd?.Select((GenericDeviceMessageAttributes x, int i) => new EnumeratePair<GenericDeviceMessageAttributes>(x, i)).ToList().ForEach(delegate(EnumeratePair<GenericDeviceMessageAttributes> x)
{
x.attr._index = (uint)x.index;
});
RotateCmd?.Select((GenericDeviceMessageAttributes x, int i) => new EnumeratePair<GenericDeviceMessageAttributes>(x, i)).ToList().ForEach(delegate(EnumeratePair<GenericDeviceMessageAttributes> x)
{
x.attr._index = (uint)x.index;
});
LinearCmd?.Select((GenericDeviceMessageAttributes x, int i) => new EnumeratePair<GenericDeviceMessageAttributes>(x, i)).ToList().ForEach(delegate(EnumeratePair<GenericDeviceMessageAttributes> x)
{
x.attr._index = (uint)x.index;
});
SensorReadCmd?.Select((SensorDeviceMessageAttributes x, int i) => new EnumeratePair<SensorDeviceMessageAttributes>(x, i)).ToList().ForEach(delegate(EnumeratePair<SensorDeviceMessageAttributes> x)
{
x.attr._index = (uint)x.index;
});
SensorSubscribeCmd?.Select((SensorDeviceMessageAttributes x, int i) => new EnumeratePair<SensorDeviceMessageAttributes>(x, i)).ToList().ForEach(delegate(EnumeratePair<SensorDeviceMessageAttributes> x)
{
x.attr._index = (uint)x.index;
});
}
}
public class MessageReceivedEventArgs : EventArgs
{
public ButtplugMessage Message { get; }
public MessageReceivedEventArgs(ButtplugMessage message)
{
Message = message;
}
}
[ButtplugMessageMetadata("Ok")]
public class Ok : ButtplugMessage, IButtplugMessageOutgoingOnly
{
public Ok(uint id)
: base(id)
{
}
}
[ButtplugMessageMetadata("Test")]
public class Test : ButtplugMessage
{
private string _testStringImpl;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public string TestString
{
get
{
return _testStringImpl;
}
set
{
if (value == "Error")
{
throw new ArgumentException("Got an Error Message");
}
_testStringImpl = value;
}
}
public Test(string str, uint id = 1u)
: base(id)
{
TestString = str;
}
}
[ButtplugMessageMetadata("Error")]
public class Error : ButtplugMessage, IButtplugMessageOutgoingOnly
{
public enum ErrorClass
{
ERROR_UNKNOWN,
ERROR_INIT,
ERROR_PING,
ERROR_MSG,
ERROR_DEVICE
}
[JsonProperty(/*Could not decode attribute arguments.*/)]
public ErrorClass ErrorCode;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public string ErrorMessage;
public Error(string errorMessage, ErrorClass errorCode, uint id)
: base(id)
{
ErrorMessage = errorMessage;
ErrorCode = errorCode;
}
}
public class MessageAttributes : IEquatable<MessageAttributes>
{
[JsonProperty(/*Could not decode attribute arguments.*/)]
public uint? FeatureCount;
public MessageAttributes()
{
}
public MessageAttributes(uint featureCount)
{
FeatureCount = featureCount;
}
public bool Equals(MessageAttributes attrs)
{
return FeatureCount == attrs.FeatureCount;
}
}
public class DeviceMessageInfo : IButtplugDeviceInfoMessage
{
[JsonProperty(/*Could not decode attribute arguments.*/)]
public readonly string DeviceName;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public readonly uint DeviceIndex;
public readonly string DeviceDisplayName;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public readonly uint DeviceMessageTimingGap;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public readonly DeviceMessageAttributes DeviceMessages;
string IButtplugDeviceInfoMessage.DeviceName => DeviceName;
uint IButtplugDeviceInfoMessage.DeviceIndex => DeviceIndex;
DeviceMessageAttributes IButtplugDeviceInfoMessage.DeviceMessages => DeviceMessages;
string IButtplugDeviceInfoMessage.DeviceDisplayName => DeviceDisplayName;
uint IButtplugDeviceInfoMessage.DeviceMessageTimingGap => DeviceMessageTimingGap;
public DeviceMessageInfo(uint index, string name, DeviceMessageAttributes messages)
{
DeviceName = name;
DeviceIndex = index;
DeviceMessages = messages;
}
}
[ButtplugMessageMetadata("DeviceList")]
public class DeviceList : ButtplugMessage, IButtplugMessageOutgoingOnly
{
[JsonProperty(/*Could not decode attribute arguments.*/)]
public readonly DeviceMessageInfo[] Devices = new DeviceMessageInfo[0];
public DeviceList(DeviceMessageInfo[] deviceList, uint id)
: base(id)
{
Devices = deviceList;
}
internal DeviceList()
: base(0u)
{
}
}
[ButtplugMessageMetadata("DeviceAdded")]
public class DeviceAdded : ButtplugDeviceMessage, IButtplugMessageOutgoingOnly, IButtplugDeviceInfoMessage
{
[JsonProperty(/*Could not decode attribute arguments.*/)]
public string DeviceName;
public readonly string DeviceDisplayName;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public readonly uint DeviceMessageTimingGap;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public readonly DeviceMessageAttributes DeviceMessages;
string IButtplugDeviceInfoMessage.DeviceName => DeviceName;
uint IButtplugDeviceInfoMessage.DeviceIndex => base.DeviceIndex;
DeviceMessageAttributes IButtplugDeviceInfoMessage.DeviceMessages => DeviceMessages;
string IButtplugDeviceInfoMessage.DeviceDisplayName => DeviceDisplayName;
uint IButtplugDeviceInfoMessage.DeviceMessageTimingGap => DeviceMessageTimingGap;
public DeviceAdded(uint index, string name, DeviceMessageAttributes messages)
: base(0u, index)
{
DeviceName = name;
DeviceMessages = messages;
}
internal DeviceAdded()
: base(0u)
{
}
}
[ButtplugMessageMetadata("DeviceRemoved")]
public class DeviceRemoved : ButtplugMessage, IButtplugMessageOutgoingOnly
{
[JsonProperty(/*Could not decode attribute arguments.*/)]
public readonly uint DeviceIndex;
public DeviceRemoved(uint index)
: base(0u)
{
DeviceIndex = index;
}
}
[ButtplugMessageMetadata("RequestDeviceList")]
public class RequestDeviceList : ButtplugMessage
{
public RequestDeviceList(uint id = 1u)
: base(id)
{
}
}
[ButtplugMessageMetadata("StartScanning")]
public class StartScanning : ButtplugMessage
{
public StartScanning(uint id = 1u)
: base(id)
{
}
}
[ButtplugMessageMetadata("StopScanning")]
public class StopScanning : ButtplugMessage
{
public StopScanning(uint id = 1u)
: base(id)
{
}
}
[ButtplugMessageMetadata("ScanningFinished")]
public class ScanningFinished : ButtplugMessage, IButtplugMessageOutgoingOnly
{
public ScanningFinished()
: base(0u)
{
}
}
[ButtplugMessageMetadata("RequestServerInfo")]
public class RequestServerInfo : ButtplugMessage
{
[JsonProperty(/*Could not decode attribute arguments.*/)]
public string ClientName;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public uint MessageVersion;
public RequestServerInfo(string clientName, uint id = 1u, uint schemversion = 3u)
: base(id)
{
ClientName = clientName;
MessageVersion = schemversion;
}
}
[ButtplugMessageMetadata("ServerInfo")]
public class ServerInfo : ButtplugMessage, IButtplugMessageOutgoingOnly
{
[JsonProperty(/*Could not decode attribute arguments.*/)]
public uint MessageVersion;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public uint MaxPingTime;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public string ServerName;
public ServerInfo(string serverName, uint messageVersion, uint maxPingTime, uint id = 1u)
: base(id)
{
ServerName = serverName;
MessageVersion = messageVersion;
MaxPingTime = maxPingTime;
}
}
[ButtplugMessageMetadata("Ping")]
public class Ping : ButtplugMessage
{
public Ping(uint id = 1u)
: base(id)
{
}
}
public class GenericMessageSubcommand
{
[JsonProperty(/*Could not decode attribute arguments.*/)]
public uint Index;
protected GenericMessageSubcommand(uint index)
{
Index = index;
}
}
[ButtplugMessageMetadata("ScalarCmd")]
public class ScalarCmd : ButtplugDeviceMessage
{
public class ScalarCommand
{
public readonly uint index;
public readonly double scalar;
public ScalarCommand(uint index, double scalar)
{
this.index = index;
this.scalar = scalar;
}
}
public class ScalarSubcommand : GenericMessageSubcommand
{
private double _scalarImpl;
public readonly ActuatorType ActuatorType;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public double Scalar
{
get
{
return _scalarImpl;
}
set
{
if (value < 0.0)
{
throw new ArgumentException("ScalarCmd value cannot be less than 0!");
}
if (value > 1.0)
{
throw new ArgumentException("ScalarCmd value cannot be greater than 1!");
}
_scalarImpl = value;
}
}
public ScalarSubcommand(uint index, double scalar, ActuatorType actuatorType)
: base(index)
{
Scalar = scalar;
ActuatorType = actuatorType;
}
}
[JsonProperty(/*Could not decode attribute arguments.*/)]
public List<ScalarSubcommand> Scalars;
[JsonConstructor]
public ScalarCmd(uint deviceIndex, List<ScalarSubcommand> scalars, uint id = 1u)
: base(id, deviceIndex)
{
Scalars = scalars;
}
public ScalarCmd(List<ScalarSubcommand> scalars)
: this(uint.MaxValue, scalars)
{
}
}
[ButtplugMessageMetadata("RotateCmd")]
public class RotateCmd : ButtplugDeviceMessage
{
public class RotateCommand
{
public readonly double speed;
public readonly bool clockwise;
public RotateCommand(double speed, bool clockwise)
{
this.speed = speed;
this.clockwise = clockwise;
}
}
public class RotateSubcommand : GenericMessageSubcommand
{
private double _speedImpl;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public bool Clockwise;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public double Speed
{
get
{
return _speedImpl;
}
set
{
if (value < 0.0)
{
throw new ArgumentException("RotateCmd Speed cannot be less than 0!");
}
if (value > 1.0)
{
throw new ArgumentException("RotateCmd Speed cannot be greater than 1!");
}
_speedImpl = value;
}
}
public RotateSubcommand(uint index, double speed, bool clockwise)
: base(index)
{
Speed = speed;
Clockwise = clockwise;
}
}
[JsonProperty(/*Could not decode attribute arguments.*/)]
public List<RotateSubcommand> Rotations;
public static RotateCmd Create(double speed, bool clockwise, uint cmdCount)
{
return Create(uint.MaxValue, 1u, Enumerable.Repeat(new RotateCommand(speed, clockwise), (int)cmdCount));
}
public static RotateCmd Create(IEnumerable<RotateCommand> cmds)
{
return Create(uint.MaxValue, 1u, cmds);
}
public static RotateCmd Create(uint deviceIndex, uint msgId, double speed, bool clockwise, uint cmdCount)
{
return Create(deviceIndex, msgId, Enumerable.Repeat(new RotateCommand(speed, clockwise), (int)cmdCount));
}
public static RotateCmd Create(uint deviceIndex, uint msgId, IEnumerable<RotateCommand> cmds)
{
List<RotateSubcommand> list = new List<RotateSubcommand>(cmds.Count());
uint num = 0u;
foreach (RotateCommand cmd in cmds)
{
list.Add(new RotateSubcommand(num, cmd.speed, cmd.clockwise));
num++;
}
return new RotateCmd(deviceIndex, list, msgId);
}
[JsonConstructor]
public RotateCmd(uint deviceIndex, List<RotateSubcommand> rotations, uint id = 1u)
: base(id, deviceIndex)
{
Rotations = rotations;
}
public RotateCmd(List<RotateSubcommand> rotations)
: this(uint.MaxValue, rotations)
{
}
}
[ButtplugMessageMetadata("LinearCmd")]
public class LinearCmd : ButtplugDeviceMessage
{
public class VectorCommand
{
public readonly double position;
public readonly uint duration;
public VectorCommand(double position, uint duration)
{
this.position = position;
this.duration = duration;
}
}
public class VectorSubcommand : GenericMessageSubcommand
{
private double _positionImpl;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public uint Duration;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public double Position
{
get
{
return _positionImpl;
}
set
{
if (value < 0.0)
{
throw new ArgumentException("LinearCmd Speed cannot be less than 0!");
}
if (value > 1.0)
{
throw new ArgumentException("LinearCmd Speed cannot be greater than 1!");
}
_positionImpl = value;
}
}
public VectorSubcommand(uint index, uint duration, double position)
: base(index)
{
Duration = duration;
Position = position;
}
}
[JsonProperty(/*Could not decode attribute arguments.*/)]
public List<VectorSubcommand> Vectors;
public static LinearCmd Create(uint duration, double position, uint cmdCount)
{
return Create(uint.MaxValue, 1u, Enumerable.Repeat(new VectorCommand(position, duration), (int)cmdCount));
}
public static LinearCmd Create(uint deviceIndex, uint msgId, uint duration, double position, uint cmdCount)
{
return Create(deviceIndex, msgId, Enumerable.Repeat(new VectorCommand(position, duration), (int)cmdCount));
}
public static LinearCmd Create(IEnumerable<VectorCommand> cmds)
{
return Create(uint.MaxValue, 1u, cmds);
}
public static LinearCmd Create(uint deviceIndex, uint msgId, IEnumerable<VectorCommand> cmds)
{
List<VectorSubcommand> list = new List<VectorSubcommand>(cmds.Count());
uint num = 0u;
foreach (VectorCommand cmd in cmds)
{
list.Add(new VectorSubcommand(num, cmd.duration, cmd.position));
num++;
}
return new LinearCmd(deviceIndex, list, msgId);
}
[JsonConstructor]
public LinearCmd(uint deviceIndex, List<VectorSubcommand> vectors, uint id = 1u)
: base(id, deviceIndex)
{
Vectors = vectors;
}
public LinearCmd(List<VectorSubcommand> vectors)
: this(uint.MaxValue, vectors)
{
}
}
[ButtplugMessageMetadata("StopDeviceCmd")]
public class StopDeviceCmd : ButtplugDeviceMessage
{
public StopDeviceCmd(uint deviceIndex = uint.MaxValue, uint id = 1u)
: base(id, deviceIndex)
{
}
}
[ButtplugMessageMetadata("StopAllDevices")]
public class StopAllDevices : ButtplugMessage
{
public StopAllDevices(uint id = 1u)
: base(id)
{
}
}
[ButtplugMessageMetadata("SensorReadCmd")]
public class SensorReadCmd : ButtplugDeviceMessage
{
[JsonProperty(/*Could not decode attribute arguments.*/)]
public uint SensorIndex;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public SensorType SensorType;
[JsonConstructor]
public SensorReadCmd(uint deviceIndex, uint sensorIndex, SensorType sensorType, uint id = 1u)
: base(id, deviceIndex)
{
SensorIndex = sensorIndex;
SensorType = sensorType;
}
public SensorReadCmd(uint sensorIndex, SensorType sensorType)
: this(uint.MaxValue, sensorIndex, sensorType)
{
}
}
[ButtplugMessageMetadata("SensorReading")]
public class SensorReading : ButtplugDeviceMessage
{
[JsonProperty(/*Could not decode attribute arguments.*/)]
public readonly uint SensorIndex;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public readonly SensorType SensorType;
[JsonProperty(/*Could not decode attribute arguments.*/)]
public readonly List<int> data;
}
}
namespace Buttplug.Client
{
public class ButtplugClient : IDisposable, IAsyncDisposable
{
protected Timer _pingTimer;
internal ButtplugClientMessageHandler _handler;
private readonly ConcurrentDictionary<uint, ButtplugClientDevice> _devices = new ConcurrentDictionary<uint, ButtplugClientDevice>();
private IButtplugClientConnector _connector;
public string Name { get; }
public ButtplugClientDevice[] Devices => _devices.Values.ToArray();
public bool Connected => _connector?.Connected ?? false;
public event EventHandler<DeviceAddedEventArgs> DeviceAdded;
public event EventHandler<DeviceRemovedEventArgs> DeviceRemoved;
public event EventHandler<ButtplugExceptionEventArgs> ErrorReceived;
public event EventHandler ScanningFinished;
public event EventHandler PingTimeout;
public event EventHandler ServerDisconnect;
public ButtplugClient(string clientName)
{
Name = clientName;
}
public async Task ConnectAsync(IButtplugClientConnector connector, CancellationToken token = default(CancellationToken))
{
if (Connected)
{
throw new ButtplugHandshakeException("Client already connected to a server.");
}
ButtplugUtils.ArgumentNotNull(connector, "connector");
_connector = connector;
_connector.Disconnected += delegate(object obj, EventArgs eventArgs)
{
this.ServerDisconnect?.Invoke(obj, eventArgs);
};
_connector.InvalidMessageReceived += ConnectorErrorHandler;
_connector.MessageReceived += MessageReceivedHandler;
_devices.Clear();
_handler = new ButtplugClientMessageHandler(connector);
await _connector.ConnectAsync(token).ConfigureAwait(continueOnCapturedContext: false);
ButtplugMessage res = await _handler.SendMessageAsync(new RequestServerInfo(Name), token).ConfigureAwait(continueOnCapturedContext: false);
if (!(res is ServerInfo si))
{
if (res is Error e)
{
await DisconnectAsync().ConfigureAwait(continueOnCapturedContext: false);
throw ButtplugException.FromError(e);
}
await DisconnectAsync().ConfigureAwait(continueOnCapturedContext: false);
throw new ButtplugHandshakeException("Unrecognized message " + res.Name + " during handshake", res.Id);
}
if (si.MaxPingTime != 0)
{
_pingTimer?.Dispose();
_pingTimer = new Timer(OnPingTimer, null, 0, Convert.ToInt32(Math.Round((double)si.MaxPingTime / 2.0, 0)));
}
if (si.MessageVersion < 3)
{
await DisconnectAsync().ConfigureAwait(continueOnCapturedContext: false);
throw new ButtplugHandshakeException($"Buttplug Server's schema version ({si.MessageVersion}) is less than the client's ({3u}). A newer server is required.", res.Id);
}
ButtplugMessage resp = await _handler.SendMessageAsync(new RequestDeviceList()).ConfigureAwait(continueOnCapturedContext: false);
if (resp is DeviceList deviceList)
{
DeviceMessageInfo[] devices = deviceList.Devices;
foreach (DeviceMessageInfo deviceMessageInfo in devices)
{
if (!_devices.ContainsKey(deviceMessageInfo.DeviceIndex))
{
ButtplugClientDevice buttplugClientDevice = new ButtplugClientDevice(_handler, deviceMessageInfo);
_devices[deviceMessageInfo.DeviceIndex] = buttplugClientDevice;
this.DeviceAdded?.Invoke(this, new DeviceAddedEventArgs(buttplugClientDevice));
}
}
return;
}
await DisconnectAsync().ConfigureAwait(continueOnCapturedContext: false);
if (resp is Error msg)
{
throw ButtplugException.FromError(msg);
}
throw new ButtplugHandshakeException("Received unknown response to DeviceList handshake query");
}
public async Task DisconnectAsync()
{
if (Connected)
{
_connector.MessageReceived -= MessageReceivedHandler;
await _connector.DisconnectAsync().ConfigureAwait(continueOnCapturedContext: false);
this.ServerDisconnect?.Invoke(this, EventArgs.Empty);
}
}
public async Task StartScanningAsync(CancellationToken token = default(CancellationToken))
{
await _handler.SendMessageExpectOk(new StartScanning(), token).ConfigureAwait(continueOnCapturedContext: false);
}
public async Task StopScanningAsync(CancellationToken token = default(CancellationToken))
{
await _handler.SendMessageExpectOk(new StopScanning(), token).ConfigureAwait(continueOnCapturedContext: false);
}
public async Task StopAllDevicesAsync(CancellationToken token = default(CancellationToken))
{
await _handler.SendMessageExpectOk(new StopAllDevices(), token).ConfigureAwait(continueOnCapturedContext: false);
}
private void ConnectorErrorHandler(object sender, ButtplugExceptionEventArgs exception)
{
this.ErrorReceived?.Invoke(this, exception);
}
private async void MessageReceivedHandler(object sender, MessageReceivedEventArgs args)
{
ButtplugMessage message = args.Message;
if (!(message is DeviceAdded deviceAdded))
{
ButtplugClientDevice value;
if (!(message is DeviceRemoved deviceRemoved))
{
if (!(message is ScanningFinished))
{
if (message is Error error)
{
this.ErrorReceived?.Invoke(this, new ButtplugExceptionEventArgs(ButtplugException.FromError(error)));
if (error.ErrorCode == Error.ErrorClass.ERROR_PING)
{
this.PingTimeout?.Invoke(this, EventArgs.Empty);
await DisconnectAsync().ConfigureAwait(continueOnCapturedContext: false);
}
}
else
{
this.ErrorReceived?.Invoke(this, new ButtplugExceptionEventArgs(new ButtplugMessageException($"Got unhandled message: {message}", message.Id)));
}
}
else
{
this.ScanningFinished?.Invoke(this, EventArgs.Empty);
}
}
else if (!_devices.ContainsKey(deviceRemoved.DeviceIndex))
{
this.ErrorReceived?.Invoke(this, new ButtplugExceptionEventArgs(new ButtplugDeviceException("Got device removed message for unknown device.", message.Id)));
}
else if (_devices.TryRemove(deviceRemoved.DeviceIndex, out value))
{
this.DeviceRemoved?.Invoke(this, new DeviceRemovedEventArgs(value));
}
}
else
{
ButtplugClientDevice dev = new ButtplugClientDevice(_handler, deviceAdded);
_devices.AddOrUpdate(deviceAdded.DeviceIndex, dev, (uint u, ButtplugClientDevice device) => dev);
this.DeviceAdded?.Invoke(this, new DeviceAddedEventArgs(dev));
}
}
private async void OnPingTimer(object state)
{
try
{
await _handler.SendMessageExpectOk(new Ping()).ConfigureAwait(continueOnCapturedContext: false);
}
catch (Exception inner)
{
this.ErrorReceived?.Invoke(this, new ButtplugExceptionEventArgs(new ButtplugPingException("Exception thrown during ping update", 0u, inner)));
await DisconnectAsync().ConfigureAwait(continueOnCapturedContext: false);
}
}
protected virtual void Dispose(bool disposing)
{
DisconnectAsync().GetAwaiter().GetResult();
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual async ValueTask DisposeAsync(bool disposing)
{
await DisconnectAsync();
}
public async ValueTask DisposeAsync()
{
await DisposeAsync(disposing: true);
GC.SuppressFinalize(this);
}
}
public class ButtplugClientConnectorException : ButtplugException
{
public ButtplugClientConnectorException(string message, Exception inner = null)
: base(message, Error.ErrorClass.ERROR_UNKNOWN, 0u, inner)
{
}
}
public class ButtplugClientDevice
{
private readonly ButtplugClientMessageHandler _handler;
public uint Index { get; }
public string Name { get; }
public string DisplayName { get; }
public uint MessageTimingGap { get; }
public DeviceMessageAttributes MessageAttributes { get; }
public List<GenericDeviceMessageAttributes> VibrateAttributes => GenericAcutatorAttributes(ActuatorType.Vibrate);
public List<GenericDeviceMessageAttributes> OscillateAttributes => GenericAcutatorAttributes(ActuatorType.Oscillate);
public List<GenericDeviceMessageAttributes> RotateAttributes
{
get
{
if (MessageAttributes.RotateCmd != null)
{
return MessageAttributes.RotateCmd.ToList();
}
return Enumerable.Empty<GenericDeviceMessageAttributes>().ToList();
}
}
public List<GenericDeviceMessageAttributes> LinearAttributes
{
get
{
if (MessageAttributes.LinearCmd != null)
{
return MessageAttributes.LinearCmd.ToList();
}
return Enumerable.Empty<GenericDeviceMessageAttributes>().ToList();
}
}
public bool HasBattery => SensorReadAttributes(SensorType.Battery).Any();
internal ButtplugClientDevice(ButtplugClientMessageHandler handler, IButtplugDeviceInfoMessage devInfo)
: this(handler, devInfo.DeviceIndex, devInfo.DeviceName, devInfo.DeviceMessages, devInfo.DeviceDisplayName, devInfo.DeviceMessageTimingGap)
{
ButtplugUtils.ArgumentNotNull(devInfo, "devInfo");
}
internal ButtplugClientDevice(ButtplugClientMessageHandler handler, uint index, string name, DeviceMessageAttributes messages, string displayName, uint messageTimingGap)
{
ButtplugUtils.ArgumentNotNull(handler, "handler");
_handler = handler;
Index = index;
Name = name;
MessageAttributes = messages;
DisplayName = displayName;
MessageTimingGap = messageTimingGap;
}
public List<GenericDeviceMessageAttributes> GenericAcutatorAttributes(ActuatorType actuator)
{
if (MessageAttributes.ScalarCmd != null)
{
return MessageAttributes.ScalarCmd.Where((GenericDeviceMessageAttributes x) => x.ActuatorType == actuator).ToList();
}
return Enumerable.Empty<GenericDeviceMessageAttributes>().ToList();
}
public async Task ScalarAsync(ScalarCmd.ScalarSubcommand command)
{
List<ScalarCmd.ScalarSubcommand> scalars = new List<ScalarCmd.ScalarSubcommand>();
GenericAcutatorAttributes(command.ActuatorType).ForEach(delegate(GenericDeviceMessageAttributes x)
{
scalars.Add(new ScalarCmd.ScalarSubcommand(x.Index, command.Scalar, command.ActuatorType));
});
if (!scalars.Any())
{
throw new ButtplugDeviceException("Scalar command for device " + Name + " did not generate any commands. Are you sure the device supports the ActuatorType sent?");
}
await _handler.SendMessageExpectOk(new ScalarCmd(Index, scalars)).ConfigureAwait(continueOnCapturedContext: false);
}
public async Task ScalarAsync(List<ScalarCmd.ScalarSubcommand> command)
{
if (!command.Any())
{
throw new ArgumentException("Command List for ScalarAsync must have at least 1 command.");
}
await _handler.SendMessageExpectOk(new ScalarCmd(Index, command)).ConfigureAwait(continueOnCapturedContext: false);
}
public async Task VibrateAsync(double speed)
{
await ScalarAsync(new ScalarCmd.ScalarSubcommand(uint.MaxValue, speed, ActuatorType.Vibrate));
}
public async Task VibrateAsync(IEnumerable<double> cmds)
{
List<GenericDeviceMessageAttributes> vibrateAttributes = VibrateAttributes;
if (cmds.Count() > vibrateAttributes.Count())
{
throw new ButtplugDeviceException($"Device {Name} only has {vibrateAttributes.Count()} vibrators, but {cmds.Count()} commands given.");
}
await ScalarAsync(vibrateAttributes.Select((GenericDeviceMessageAttributes x, int i) => new ScalarCmd.ScalarSubcommand(x.Index, cmds.ElementAt(i), ActuatorType.Vibrate)).ToList()).ConfigureAwait(continueOnCapturedContext: false);
}
public async Task VibrateAsync(IEnumerable<ScalarCmd.ScalarCommand> cmds)
{
await ScalarAsync(cmds.Select((ScalarCmd.ScalarCommand x) => new ScalarCmd.ScalarSubcommand(x.index, x.scalar, ActuatorType.Vibrate)).ToList()).ConfigureAwait(continueOnCapturedContext: false);
}
public async Task OscillateAsync(double speed)
{
await ScalarAsync(new ScalarCmd.ScalarSubcommand(uint.MaxValue, speed, ActuatorType.Oscillate));
}
public async Task OscillateAsync(IEnumerable<double> cmds)
{
List<GenericDeviceMessageAttributes> oscillateAttributes = OscillateAttributes;
if (cmds.Count() > oscillateAttributes.Count())
{
throw new ButtplugDeviceException($"Device {Name} only has {oscillateAttributes.Count()} vibrators, but {cmds.Count()} commands given.");
}
await ScalarAsync(oscillateAttributes.Select((GenericDeviceMessageAttributes x, int i) => new ScalarCmd.ScalarSubcommand(x.Index, cmds.ElementAt(i), ActuatorType.Oscillate)).ToList()).ConfigureAwait(continueOnCapturedContext: false);
}
public async Task OscillateAsync(IEnumerable<ScalarCmd.ScalarCommand> cmds)
{
await ScalarAsync(cmds.Select((ScalarCmd.ScalarCommand x) => new ScalarCmd.ScalarSubcommand(x.index, x.scalar, ActuatorType.Oscillate)).ToList()).ConfigureAwait(continueOnCapturedContext: false);
}
public async Task RotateAsync(double speed, bool clockwise)
{
if (!RotateAttributes.Any())
{
throw new ButtplugDeviceException("Device " + Name + " does not support rotation");
}
RotateCmd rotateCmd = RotateCmd.Create(speed, clockwise, (uint)RotateAttributes.Count);
rotateCmd.DeviceIndex = Index;
await _handler.SendMessageExpectOk(rotateCmd).ConfigureAwait(continueOnCapturedContext: false);
}
public async Task RotateAsync(IEnumerable<RotateCmd.RotateCommand> cmds)
{
if (!RotateAttributes.Any())
{
throw new ButtplugDeviceException("Device " + Name + " does not support rotation");
}
RotateCmd rotateCmd = RotateCmd.Create(cmds);
rotateCmd.DeviceIndex = Index;
await _handler.SendMessageExpectOk(rotateCmd).ConfigureAwait(continueOnCapturedContext: false);
}
public async Task LinearAsync(uint duration, double position)
{
if (!LinearAttributes.Any())
{
throw new ButtplugDeviceException("Device " + Name + " does not support linear position");
}
LinearCmd linearCmd = LinearCmd.Create(duration, position, (uint)LinearAttributes.Count);
linearCmd.DeviceIndex = Index;
await _handler.SendMessageExpectOk(linearCmd).ConfigureAwait(continueOnCapturedContext: false);
}
public async Task LinearAsync(IEnumerable<LinearCmd.VectorCommand> cmds)
{
if (!LinearAttributes.Any())
{
throw new ButtplugDeviceException("Device " + Name + " does not support linear position");
}
LinearCmd linearCmd = LinearCmd.Create(cmds);
linearCmd.DeviceIndex = Index;
await _handler.SendMessageExpectOk(linearCmd).ConfigureAwait(continueOnCapturedContext: false);
}
public List<SensorDeviceMessageAttributes> SensorReadAttributes(SensorType sensor)
{
if (MessageAttributes.SensorReadCmd != null)
{
return MessageAttributes.SensorReadCmd.Where((SensorDeviceMessageAttributes x) => x.SensorType == sensor).ToList();
}
return Enumerable.Empty<SensorDeviceMessageAttributes>().ToList();
}
public async Task<double> BatteryAsync()
{
if (!HasBattery)
{
throw new ButtplugDeviceException("Device " + Name + " does not have battery capabilities.");
}
ButtplugMessage buttplugMessage = await _handler.SendMessageAsync(new SensorReadCmd(Index, SensorReadAttributes(SensorType.Battery).ElementAt(0).Index, SensorType.Battery)).ConfigureAwait(continueOnCapturedContext: false);
if (!(buttplugMessage is SensorReading sensorReading))
{
if (buttplugMessage is Error msg)
{
throw ButtplugException.FromError(msg);
}
throw new ButtplugMessageException("Message type " + buttplugMessage.Name + " not handled by BatteryAsync", buttplugMessage.Id);
}
return (double)sensorReading.data[0] / 100.0;
}
public async Task Stop()
{
await _handler.SendMessageExpectOk(new StopDeviceCmd(Index)).ConfigureAwait(continueOnCapturedContext: false);
}
}
internal class ButtplugClientMessageHandler
{
private IButtplugClientConnector _connector;
internal ButtplugClientMessageHandler(IButtplugClientConnector connector)
{
_connector = connector;
}
public async Task<ButtplugMessage> SendMessageAsync(ButtplugMessage msg, CancellationToken token = default(CancellationToken))
{
if (!_connector.Connected)
{
throw new ButtplugClientConnectorException("Client not connected.");
}
return await _connector.SendAsync(msg, token).ConfigureAwait(continueOnCapturedContext: false);
}
public async Task SendMessageExpectOk(ButtplugMessage msg, CancellationToken token = default(CancellationToken))
{
ButtplugMessage buttplugMessage = await SendMessageAsync(msg, token).ConfigureAwait(continueOnCapturedContext: false);
if (!(buttplugMessage is Ok))
{
if (buttplugMessage is Error msg2)
{
throw ButtplugException.FromError(msg2);
}
throw new ButtplugMessageException("Message type " + msg.Name + " not handled by SendMessageExpectOk", msg.Id);
}
}
}
public class ButtplugConnectorJSONParser
{
private readonly ButtplugJsonMessageParser _parser = new ButtplugJsonMessageParser();
public string Serialize(ButtplugMessage msg)
{
return _parser.Serialize(msg);
}
public string Serialize(ButtplugMessage[] msgs)
{
return _parser.Serialize(msgs);
}
public IEnumerable<ButtplugMessage> Deserialize(string msg)
{
return _parser.Deserialize(msg);
}
}
public class ButtplugConnectorMessageSorter : IDisposable
{
private int _counter;
private readonly ConcurrentDictionary<uint, TaskCompletionSource<ButtplugMessage>> _waitingMsgs = new ConcurrentDictionary<uint, TaskCompletionSource<ButtplugMessage>>();
public uint NextMsgId => Convert.ToUInt32(Interlocked.Increment(ref _counter));
public Task<ButtplugMessage> PrepareMessage(ButtplugMessage msg)
{
msg.Id = NextMsgId;
TaskCompletionSource<ButtplugMessage> taskCompletionSource = new TaskCompletionSource<ButtplugMessage>();
_waitingMsgs.TryAdd(msg.Id, taskCompletionSource);
return taskCompletionSource.Task;
}
public void CheckMessage(ButtplugMessage msg)
{
if (msg.Id == 0)
{
throw new ButtplugMessageException("Cannot sort message with System ID", msg.Id);
}
if (!_waitingMsgs.TryRemove(msg.Id, out var value))
{
throw new ButtplugMessageException("Message with non-matching ID received.", msg.Id);
}
if (msg is Error msg2)
{
value.SetException(ButtplugException.FromError(msg2));
}
else
{
value.SetResult(msg);
}
}
protected virtual void Dispose(bool disposing)
{
foreach (TaskCompletionSource<ButtplugMessage> value in _waitingMsgs.Values)
{
value.TrySetException(new Exception("Sorter has been destroyed with live tasks still in queue."));
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
public class ReturnMessage
{
public readonly string Message;
public readonly Task<ButtplugMessage> Promise;
public ReturnMessage(string message, Task<ButtplugMessage> promise)
{
Message = message;
Promise = promise;
}
}
public class ButtplugRemoteJSONConnector : IDisposable
{
private readonly ButtplugConnectorJSONParser _jsonSerializer = new ButtplugConnectorJSONParser();
private readonly ButtplugConnectorMessageSorter _msgSorter = new ButtplugConnectorMessageSorter();
public event EventHandler<MessageReceivedEventArgs> MessageReceived;
public event EventHandler<ButtplugExceptionEventArgs> InvalidMessageReceived;
public ReturnMessage PrepareMessage(ButtplugMessage msg)
{
Task<ButtplugMessage> promise = _msgSorter.PrepareMessage(msg);
return new ReturnMessage(_jsonSerializer.Serialize(msg), promise);
}
protected void ReceiveMessages(string jSONMsg)
{
IEnumerable<ButtplugMessage> enumerable;
try
{
enumerable = _jsonSerializer.Deserialize(jSONMsg);
}
catch (ButtplugMessageException ex)
{
this.InvalidMessageReceived?.Invoke(this, new ButtplugExceptionEventArgs(ex));
return;
}
foreach (ButtplugMessage item in enumerable)
{
if (item.Id == 0)
{
this.MessageReceived?.Invoke(this, new MessageReceivedEventArgs(item));
continue;
}
try
{
_msgSorter.CheckMessage(item);
}
catch (ButtplugMessageException ex2)
{
this.InvalidMessageReceived?.Invoke(this, new ButtplugExceptionEventArgs(ex2));
}
}
}
protected virtual void Dispose(bool disposing)
{
_msgSorter.Dispose();
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
public class ButtplugWebsocketConnector : ButtplugRemoteJSONConnector, IButtplugClientConnector
{
private ClientWebSocket _wsClient;
private readonly SynchronizationContext _owningDispatcher = SynchronizationContext.Current ?? new SynchronizationContext();
private readonly Uri _uri;
private Task _readTask;
public bool Connected
{
get
{
ClientWebSocket wsClient = _wsClient;
if (wsClient == null)
{
return false;
}
return wsClient.State == WebSocketState.Open;
}
}
public event EventHandler Disconnected;
public ButtplugWebsocketConnector(Uri uri)
{
_uri = uri;
}
public async Task ConnectAsync(CancellationToken token = default(CancellationToken))
{
if (_wsClient != null)
{
throw new ButtplugHandshakeException("Websocket connector is already connected.");
}
try
{
_wsClient = new ClientWebSocket();
await _wsClient.ConnectAsync(_uri, token).ConfigureAwait(continueOnCapturedContext: false);
}
catch (Exception inner)
{
throw new ButtplugClientConnectorException("Websocket Connection Exception! See Inner Exception", inner);
}
_readTask = Task.Run(async delegate
{
await RunClientLoop(token).ConfigureAwait(continueOnCapturedContext: false);
}, token);
}
public async Task DisconnectAsync(CancellationToken token = default(CancellationToken))
{
if (_wsClient != null && (_wsClient.State == WebSocketState.Connecting || _wsClient.State == WebSocketState.Open))
{
await _wsClient.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None).ConfigureAwait(continueOnCapturedContext: false);
}
_wsClient?.Dispose();
_wsClient = null;
await _readTask.ConfigureAwait(continueOnCapturedContext: false);
}
public async Task<ButtplugMessage> SendAsync(ButtplugMessage msg, CancellationToken cancellationToken)
{
if (_wsClient == null)
{
throw new ButtplugException("Cannot send messages while disconnected", Error.ErrorClass.ERROR_MSG);
}
ReturnMessage returnMsg = PrepareMessage(msg);
await _wsClient.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(returnMsg.Message)), WebSocketMessageType.Text, endOfMessage: true, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
return await returnMsg.Promise.ConfigureAwait(continueOnCapturedContext: false);
}
private async Task RunClientLoop(CancellationToken token)
{
try
{
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false);
byte[] buff = new byte[2048];
while (Connected && !token.IsCancellationRequested)
{
WebSocketReceiveResult webSocketReceiveResult = await _wsClient.ReceiveAsync(new ArraySegment<byte>(buff), token).ConfigureAwait(continueOnCapturedContext: false);
if (webSocketReceiveResult.MessageType == WebSocketMessageType.Text)
{
string @string = Encoding.Default.GetString(buff, 0, webSocketReceiveResult.Count);
ReceiveMessages(@string);
}
}
}
catch (Exception)
{
}
finally
{
if (_wsClient != null)
{
_wsClient.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", token).Dispose();
_wsClient = null;
}
_owningDispatcher.Send(delegate
{
Dispose();
}, null);
_owningDispatcher.Send(delegate
{
this.Disconnected?.Invoke(this, EventArgs.Empty);
}, null);
}
}
}
public class DeviceAddedEventArgs
{
public readonly ButtplugClientDevice Device;
public DeviceAddedEventArgs(ButtplugClientDevice device)
{
Device = device;
}
}
public class DeviceRemovedEventArgs
{
public readonly ButtplugClientDevice Device;
public DeviceRemovedEventArgs(ButtplugClientDevice device)
{
Device = device;
}
}
public interface IButtplugClientConnector
{
bool Connected { get; }
event EventHandler<MessageReceivedEventArgs> MessageReceived;
event EventHandler<ButtplugExceptionEventArgs> InvalidMessageReceived;
event EventHandler Disconnected;
Task ConnectAsync(CancellationToken token = default(CancellationToken));
Task DisconnectAsync(CancellationToken token = default(CancellationToken));
Task<ButtplugMessage> SendAsync(ButtplugMessage msg, CancellationToken token = default(CancellationToken));
}
}