예제 #1
0
        private void DeviceAddedHandler(object aObj, DeviceAddedEventArgs aEvent)
        {
            // If we get to 4 billion devices connected, this may be a problem.
            var deviceIndex = (uint)Interlocked.Increment(ref _deviceIndexCounter);

            // Devices can be turned off by the time they get to this point, at which point they end up null. Make sure the device isn't null.
            if (aEvent.Device == null)
            {
                return;
            }

            var duplicates = from x in _devices.Values
                             where x.Identifier == aEvent.Device.Identifier
                             select x;

            if (duplicates.Any())
            {
                _bpLogger.Trace($"Already have device {aEvent.Device.Name} in Devices list");
                return;
            }

            _bpLogger.Debug($"Adding Device {aEvent.Device.Name} at index {deviceIndex}");
            _devices.Add(deviceIndex, aEvent.Device);
            aEvent.Device.DeviceRemoved += DeviceRemovedHandler;
            var msg = new DeviceAdded(deviceIndex, aEvent.Device.Name, GetAllowedMessageTypesAsStrings(aEvent.Device).ToArray());

            DeviceMessageReceived?.Invoke(this, new MessageReceivedEventArgs(msg));
        }
        public async Task <IButtplugDevice> CreateDeviceAsync([NotNull] BluetoothLEDevice aDevice)
        {
            // GetGattServicesForUuidAsync is 15063 only
            var services = await aDevice.GetGattServicesAsync(BluetoothCacheMode.Cached);

            foreach (var s in services.Services)
            {
                _bpLogger.Trace("Found service UUID: " + s.Uuid);
            }

            var srvResult = await aDevice.GetGattServicesForUuidAsync(_deviceInfo.Services[0], BluetoothCacheMode.Cached);

            if (srvResult.Status != GattCommunicationStatus.Success || !srvResult.Services.Any())
            {
                _bpLogger.Trace("Cannot find service for device");
                return(null);
            }

            var service = srvResult.Services.First();

            var chrResult = await service.GetCharacteristicsAsync();

            if (chrResult.Status != GattCommunicationStatus.Success)
            {
                return(null);
            }

            var chrs = from x in chrResult.Characteristics
                       where _deviceInfo.Characteristics.Contains(x.Uuid)
                       select x;

            var gattCharacteristics = chrs as GattCharacteristic[] ?? chrs.ToArray();

            if (!gattCharacteristics.Any())
            {
                return(null);
            }

            // TODO This assumes we're always planning on having the UUIDs sorted in the Info classes, which is probably not true.
            var bleInterface = new UWPBluetoothDeviceInterface(_buttplugLogManager,
                                                               aDevice, gattCharacteristics.OrderBy((aChr) => aChr.Uuid).ToArray());

            var device = _deviceInfo.CreateDevice(_buttplugLogManager, bleInterface);

            if (await device.Initialize() is Ok)
            {
                return(device);
            }

            // If initialization fails, don't actually send the message back. Just return null, we'll have the info in the logs.
            return(null);
        }
 public UWPBluetoothDeviceFactory([NotNull] IButtplugLogManager aLogManager, [NotNull] IBluetoothDeviceInfo aInfo)
 {
     _buttplugLogManager = aLogManager;
     _bpLogger           = _buttplugLogManager.GetLogger(GetType());
     _bpLogger.Trace($"Creating {GetType().Name}");
     _deviceInfo = aInfo;
 }
예제 #4
0
 public HidDeviceFactory(IButtplugLogManager aLogManager, IHidDeviceInfo aInfo)
 {
     _buttplugLogManager = aLogManager;
     _bpLogger           = _buttplugLogManager.GetLogger(GetType());
     _bpLogger.Trace($"Creating {GetType().Name}");
     _deviceInfo = aInfo;
 }
예제 #5
0
        public async Task <ButtplugMessage> SendMessage([NotNull] ButtplugMessage aMsg)
        {
            _bpLogger.Trace($"Got Message {aMsg.Id} of type {aMsg.GetType().Name} to send");
            var id = aMsg.Id;

            if (id == 0)
            {
                return(_bpLogger.LogWarnMsg(id, Error.ErrorClass.ERROR_MSG,
                                            "Message Id 0 is reserved for outgoing system messages. Please use another Id."));
            }

            if (aMsg is IButtplugMessageOutgoingOnly)
            {
                return(_bpLogger.LogWarnMsg(id, Error.ErrorClass.ERROR_MSG,
                                            $"Message of type {aMsg.GetType().Name} cannot be sent to server"));
            }

            if (_pingTimedOut)
            {
                return(_bpLogger.LogErrorMsg(id, Error.ErrorClass.ERROR_PING, "Ping timed out."));
            }

            // If we get a message that's not RequestServerInfo first, return an error.
            if (!_receivedRequestServerInfo && !(aMsg is RequestServerInfo))
            {
                return(_bpLogger.LogErrorMsg(id, Error.ErrorClass.ERROR_INIT,
                                             "RequestServerInfo must be first message received by server!"));
            }

            switch (aMsg)
            {
            case RequestLog m:
                _bpLogger.Debug("Got RequestLog Message");
                BpLogManager.Level = m.LogLevel;
                return(new Ok(id));

            case Ping _:
                // Start the timer
                _pingTimer?.Change((int)_maxPingTime, (int)_maxPingTime);

                return(new Ok(id));

            case RequestServerInfo rsi:
                _bpLogger.Debug("Got RequestServerInfo Message");
                _receivedRequestServerInfo = true;
                _clientMessageVersion      = rsi.MessageVersion;

                // Start the timer
                _pingTimer?.Change((int)_maxPingTime, (int)_maxPingTime);
                ClientConnected?.Invoke(this, new MessageReceivedEventArgs(rsi));
                return(new ServerInfo(_serverName, 1, _maxPingTime, id));

            case Test m:
                return(new Test(m.TestString, id));
            }

            return(await _deviceManager.SendMessage(aMsg));
        }
예제 #6
0
 public ButtplugWSClient(string aClientName)
 {
     _clientName   = aClientName;
     _bpLogManager = new ButtplugLogManager();
     _bpLogger     = _bpLogManager.GetLogger(GetType());
     _parser       = new ButtplugJsonMessageParser(_bpLogManager);
     _bpLogger.Trace("Finished setting up ButtplugClient");
     _owningDispatcher = Dispatcher.CurrentDispatcher;
     _tokenSource      = new CancellationTokenSource();
 }
        public async Task <ButtplugMessage> SendMessage([NotNull] ButtplugMessage aMsg)
        {
            _bpLogger.Trace($"Got Message {aMsg.Id} of type {aMsg.GetType().Name} to send");
            var id = aMsg.Id;

            if (id == 0)
            {
                return(_bpLogger.LogWarnMsg(id, Error.ErrorClass.ERROR_MSG,
                                            "Message Id 0 is reserved for outgoing system messages. Please use another Id."));
            }

            if (aMsg is IButtplugMessageOutgoingOnly)
            {
                return(_bpLogger.LogWarnMsg(id, Error.ErrorClass.ERROR_MSG,
                                            $"Message of type {aMsg.GetType().Name} cannot be sent to server"));
            }

            if (_pingTimedOut)
            {
                return(_bpLogger.LogErrorMsg(id, Error.ErrorClass.ERROR_PING, "Ping timed out."));
            }

            // If we get a message that's not RequestServerInfo first, return an error.
            if (!_receivedRequestServerInfo && !(aMsg is RequestServerInfo))
            {
                return(_bpLogger.LogErrorMsg(id, Error.ErrorClass.ERROR_INIT,
                                             "RequestServerInfo must be first message received by server!"));
            }

            switch (aMsg)
            {
            case RequestLog m:
                _bpLogManager.Level = m.LogLevel;
                return(new Ok(id));

            case Ping _:
                if (_pingTimer != null)
                {
                    _pingTimer.Stop();
                    _pingTimer.Start();
                }

                return(new Ok(id));

            case RequestServerInfo _:
                _receivedRequestServerInfo = true;
                _pingTimer?.Start();
                return(new ServerInfo(_serverName, 1, _maxPingTime, id));

            case Test m:
                return(new Test(m.TestString, id));
            }

            return(await _deviceManager.SendMessage(aMsg));
        }
예제 #8
0
        public DeviceManager(IButtplugLogManager aLogManager)
        {
            _bpLogManager = aLogManager;
            _bpLogger     = _bpLogManager.GetLogger(GetType());
            _bpLogger.Trace("Setting up DeviceManager");
            _sentFinished       = true;
            _devices            = new Dictionary <uint, IButtplugDevice>();
            _deviceIndexCounter = 0;

            _managers = new List <IDeviceSubtypeManager>();
        }
        public ButtplugService([NotNull] string aServerName, uint aMaxPingTime)
        {
            _serverName   = aServerName;
            _maxPingTime  = aMaxPingTime;
            _pingTimedOut = false;
            if (aMaxPingTime != 0)
            {
                _pingTimer          = new Timer(_maxPingTime);
                _pingTimer.Elapsed += PingTimeoutHandler;
            }

            _bpLogManager = new ButtplugLogManager();
            _bpLogger     = _bpLogManager.GetLogger(GetType());
            _bpLogger.Trace("Setting up ButtplugService");
            _parser        = new ButtplugJsonMessageParser(_bpLogManager);
            _deviceManager = new DeviceManager(_bpLogManager);
            _bpLogger.Trace("Finished setting up ButtplugService");
            _deviceManager.DeviceMessageReceived += DeviceMessageReceivedHandler;
            _deviceManager.ScanningFinished      += ScanningFinishedHandler;
            _bpLogManager.LogMessageReceived     += LogMessageReceivedHandler;
        }
예제 #10
0
        public bool MayBeDevice(string advertName, List <Guid> advertGUIDs)
        {
            if (_deviceInfo.NamePrefixes.Any())
            {
                foreach (var deviceInfoNamePrefix in _deviceInfo.NamePrefixes)
                {
                    if (advertName.IndexOf(deviceInfoNamePrefix) != 0)
                    {
                        continue;
                    }

                    _bpLogger.Debug($"Found {advertName} via NamePrefix {deviceInfoNamePrefix}");
                    return(true);
                }
            }

            if ((_deviceInfo.Names.Any() && !_deviceInfo.Names.Contains(advertName)) || !_deviceInfo.Names.Any())
            {
                _bpLogger.Trace($"Dropping query for {advertName}.");
                return(false);
            }

            if (_deviceInfo.Names.Any() && !advertGUIDs.Any())
            {
                _bpLogger.Debug("Found " + advertName + " for " + _deviceInfo.GetType());
                return(true);
            }

            _bpLogger.Trace("Found " + advertName + " for " + _deviceInfo.GetType() + " with services " + advertGUIDs);
            foreach (var s in _deviceInfo.Services)
            {
                _bpLogger.Trace("Expecting " + s);
            }

            foreach (var s in advertGUIDs)
            {
                _bpLogger.Trace("Got " + s);
            }

            // Intersect doesn't intersect until the enumerator is called
            var sv = _deviceInfo.Services.Intersect(advertGUIDs);

            foreach (var s in sv)
            {
                _bpLogger.Trace("Matched " + s);
                return(true);
            }

            return(false);
        }
예제 #11
0
        public async Task <ButtplugMessage> SendMessageAsync(ButtplugMessage aMsg, CancellationToken aToken = default(CancellationToken))
        {
            ButtplugUtils.ArgumentNotNull(aMsg, nameof(aMsg));
            var id = aMsg.Id;

            switch (aMsg)
            {
            case StartScanning _:
                _bpLogger.Debug("Got StartScanning Message");
                try
                {
                    await StartScanning();
                }
                catch (ButtplugDeviceException aEx)
                {
                    // Catch and rethrow here, adding the message Id onto the exception
                    throw new ButtplugDeviceException(_bpLogger, aEx.Message, id);
                }

                return(new Ok(id));

            case StopScanning _:
                _bpLogger.Debug("Got StopScanning Message");
                StopScanning();
                return(new Ok(id));

            case StopAllDevices _:
                _bpLogger.Debug("Got StopAllDevices Message");
                var isOk     = true;
                var errorMsg = string.Empty;
                foreach (var d in Devices.ToList())
                {
                    if (!d.Value.Connected)
                    {
                        continue;
                    }

                    try
                    {
                        await d.Value.ParseMessageAsync(new StopDeviceCmd(d.Key, aMsg.Id), aToken).ConfigureAwait(false);
                    }
                    catch (ButtplugDeviceException e)
                    {
                        isOk      = false;
                        errorMsg += $"{e.Message}; ";
                    }
                }

                if (isOk)
                {
                    return(new Ok(aMsg.Id));
                }

                throw new ButtplugDeviceException(_bpLogger, errorMsg, aMsg.Id);

            case RequestDeviceList _:
                _bpLogger.Debug("Got RequestDeviceList Message");
                var msgDevices = Devices.Where(aDevice => aDevice.Value.Connected)
                                 .Select(aDevice => new DeviceMessageInfo(
                                             aDevice.Key,
                                             aDevice.Value.Name,
                                             GetAllowedMessageTypesAsDictionary(aDevice.Value, SpecVersion))).ToList();
                return(new DeviceList(msgDevices.ToArray(), id));

            // If it's a device message, it's most likely not ours.
            case ButtplugDeviceMessage m:
                _bpLogger.Trace($"Sending {aMsg.GetType().Name} to device index {m.DeviceIndex}");
                if (!Devices.ContainsKey(m.DeviceIndex))
                {
                    throw new ButtplugDeviceException(_bpLogger,
                                                      $"Dropping message for unknown device index {m.DeviceIndex}", id);
                }

                return(await Devices[m.DeviceIndex].ParseMessageAsync(m, aToken).ConfigureAwait(false));
            }

            throw new ButtplugMessageException(_bpLogger, $"Message type {aMsg.GetType().Name} unhandled by this server.", id);
        }
예제 #12
0
        public async Task <ButtplugMessage> SendMessage(ButtplugMessage aMsg)
        {
            var id = aMsg.Id;

            switch (aMsg)
            {
            case StartScanning _:
                _bpLogger.Debug("Got StartScanning Message");
                StartScanning();
                return(new Ok(id));

            case StopScanning _:
                _bpLogger.Debug("Got StopScanning Message");
                StopScanning();
                return(new Ok(id));

            case StopAllDevices _:
                _bpLogger.Debug("Got StopAllDevices Message");
                var isOk     = true;
                var errorMsg = string.Empty;
                foreach (var d in _devices.ToList())
                {
                    if (!d.Value.IsConnected)
                    {
                        continue;
                    }

                    var r = await d.Value.ParseMessage(new StopDeviceCmd(d.Key, aMsg.Id));

                    if (r is Ok)
                    {
                        continue;
                    }

                    isOk      = false;
                    errorMsg += $"{(r as Error).ErrorMessage}; ";
                }

                if (isOk)
                {
                    return(new Ok(aMsg.Id));
                }

                return(new Error(errorMsg, Error.ErrorClass.ERROR_DEVICE, aMsg.Id));

            case RequestDeviceList _:
                _bpLogger.Debug("Got RequestDeviceList Message");
                var msgDevices = _devices.Where(aDevice => aDevice.Value.IsConnected)
                                 .Select(aDevice => new DeviceMessageInfo(aDevice.Key, aDevice.Value.Name,
                                                                          GetAllowedMessageTypesAsStrings(aDevice.Value).ToArray())).ToList();
                return(new DeviceList(msgDevices.ToArray(), id));

            // If it's a device message, it's most likely not ours.
            case ButtplugDeviceMessage m:
                _bpLogger.Trace($"Sending {aMsg.GetType().Name} to device index {m.DeviceIndex}");
                if (_devices.ContainsKey(m.DeviceIndex))
                {
                    return(await _devices[m.DeviceIndex].ParseMessage(m));
                }

                return(_bpLogger.LogErrorMsg(id, Error.ErrorClass.ERROR_DEVICE, $"Dropping message for unknown device index {m.DeviceIndex}"));
            }

            return(_bpLogger.LogErrorMsg(id, Error.ErrorClass.ERROR_MSG, $"Message type {aMsg.GetType().Name} unhandled by this server."));
        }
예제 #13
0
        public async Task <ButtplugMessage> SendMessageAsync([NotNull] ButtplugMessage aMsg, CancellationToken aToken = default(CancellationToken))
        {
            var combinedToken = CancellationTokenSource.CreateLinkedTokenSource(_internalToken.Token, aToken);

            _bpLogger.Trace($"Got Message {aMsg.Id} of type {aMsg.GetType().Name} to send");
            var id = aMsg.Id;

            ButtplugUtils.ArgumentNotNull(aMsg, nameof(aMsg));

            if (id == ButtplugConsts.SystemMsgId)
            {
                throw new ButtplugMessageException(_bpLogger, "Message Id 0 is reserved for outgoing system messages. Please use another Id.", id);
            }

            if (aMsg is IButtplugMessageOutgoingOnly)
            {
                throw new ButtplugMessageException(_bpLogger, $"Message of type {aMsg.GetType().Name} cannot be sent to server", id);
            }

            if (_pingTimedOut)
            {
                throw new ButtplugPingException(_bpLogger, "Ping timed out.", id);
            }

            // If we get a message that's not RequestServerInfo first, return an error.
            if (!_receivedRequestServerInfo && !(aMsg is RequestServerInfo))
            {
                throw new ButtplugHandshakeException(_bpLogger, "RequestServerInfo must be first message received by server!", id);
            }

            _bpLogger.Debug($"Got {aMsg.Name} message.");

            switch (aMsg)
            {
            case RequestLog m:
                BpLogManager.Level = m.LogLevel;
                return(new Ok(id));

            case Ping _:
                // Start the timer
                _pingTimer?.Change((int)_maxPingTime, (int)_maxPingTime);

                return(new Ok(id));

            case RequestServerInfo rsi:
                if (_receivedRequestServerInfo)
                {
                    throw new ButtplugHandshakeException(_bpLogger, "Already received RequestServerInfo, cannot be sent twice.", id);
                }

                _receivedRequestServerInfo = true;
                _clientSpecVersion         = rsi.MessageVersion;
                _deviceManager.SpecVersion = _clientSpecVersion;

                // Start the timer
                _pingTimer?.Change((int)_maxPingTime, (int)_maxPingTime);
                _clientName = rsi.ClientName;
                ClientConnected?.Invoke(this, EventArgs.Empty);
                return(new ServerInfo(_serverName, ButtplugConsts.CurrentSpecVersion, _maxPingTime, id));

            case Core.Messages.Test m:
                return(new Core.Messages.Test(m.TestString, id));
            }

            return(await _deviceManager.SendMessageAsync(aMsg, combinedToken.Token).ConfigureAwait(false));
        }
        public ButtplugMessage[] Deserialize(string aJsonMsg)
        {
            _bpLogger?.Trace($"Got JSON Message: {aJsonMsg}");

            var      res  = new List <ButtplugMessage>();
            JsonData json = JsonMapper.ToObject(aJsonMsg);

            for (var i = 0; i < json.Count; ++i)
            {
                foreach (var key in json[i].Keys)
                {
                    if (json[i][key].Keys.Contains("Id"))
                    {
                        _bpLogger?.Trace($"Got BP {key} Message: {json[i][key].ToJson()}");
                        switch (key)
                        {
                        case "Ok":
                            res.Add(JsonUtility.FromJson <Ok>(json[i][key].ToJson()));
                            break;

                        case "Test":
                            res.Add(JsonUtility.FromJson <Test>(json[i][key].ToJson()));
                            break;

                        case "Error":
                            res.Add(JsonUtility.FromJson <Error>(json[i][key].ToJson()));
                            break;

                        case "DeviceList":
                            var dList = JsonUtility.FromJson <DeviceList>(json[i][key].ToJson());
                            List <DeviceMessageInfo> devs = new List <DeviceMessageInfo>();
                            if (json[i][key].ContainsKey("Devices") && json[i][key]["Devices"].IsArray)
                            {
                                foreach (JsonData dev in json[i][key]["Devices"])
                                {
                                    var dInfo = JsonUtility.FromJson <DeviceMessageInfo>(dev.ToJson());
                                    dInfo.DeviceMessages = new Dictionary <string, MessageAttributes>();
                                    if (dev.ContainsKey("DeviceMessages"))
                                    {
                                        foreach (var key2 in dev["DeviceMessages"].Keys)
                                        {
                                            var attr = new MessageAttributes();
                                            if (dev["DeviceMessages"][key2].ContainsKey("FeatureCount"))
                                            {
                                                attr.FeatureCount = Convert.ToUInt32(dev["DeviceMessages"][key2]["FeatureCount"].IsInt);
                                            }

                                            dInfo.DeviceMessages.Add(key2, attr);
                                        }
                                    }
                                    devs.Add(dInfo);
                                }

                                dList.Devices = devs.ToArray();
                            }

                            res.Add(dList);
                            _bpLogger.Trace("Converted back: " + JsonMapper.ToJson(res.Last()));
                            break;

                        case "DeviceAdded":
                            var dAdded = JsonUtility.FromJson <DeviceAdded>(json[i][key].ToJson());
                            dAdded.DeviceMessages = new Dictionary <string, MessageAttributes>();
                            if (json[i][key].ContainsKey("DeviceMessages"))
                            {
                                foreach (var key2 in json[i][key]["DeviceMessages"].Keys)
                                {
                                    var attr = new MessageAttributes();
                                    if (json[i][key]["DeviceMessages"][key2].ContainsKey("FeatureCount"))
                                    {
                                        attr.FeatureCount = (uint)json[i][key]["DeviceMessages"][key2]["FeatureCount"];
                                    }

                                    dAdded.DeviceMessages.Add(key2, attr);
                                }
                            }
                            res.Add(dAdded);
                            _bpLogger.Trace("Converted back: " + JsonMapper.ToJson(res.Last()));
                            break;

                        case "DeviceRemoved":
                            res.Add(JsonUtility.FromJson <DeviceRemoved>(json[i][key].ToJson()));
                            break;

                        case "ScanningFinished":
                            res.Add(JsonUtility.FromJson <ScanningFinished>(json[i][key].ToJson()));
                            break;

                        case "Log":
                            res.Add(JsonUtility.FromJson <Log>(json[i][key].ToJson()));
                            break;

                        case "ServerInfo":
                            res.Add(JsonUtility.FromJson <ServerInfo>(json[i][key].ToJson()));
                            break;

                        default:
                            _bpLogger?.Trace($"Don't know how to handle: {key}");
                            break;
                        }
                    }
                }
            }
            return(res.ToArray());
        }
예제 #15
0
        public IEnumerable <ButtplugMessage> Deserialize(string aJsonMsg)
        {
            _bpLogger.Trace($"Got JSON Message: {aJsonMsg}");
            var textReader = new StringReader(aJsonMsg);
            // While we aren't receiving from a stream here, we may get multiple JSON arrays
            // depending on how we received messages.
            var reader = new JsonTextReader(textReader)
            {
                CloseInput             = false,
                SupportMultipleContent = true,
            };

            // Aggregate all messages in the string we received. Note that we may have received
            // multiple strings, which may have multiple arrays. If any message in the array is
            // invalid, we dump the message list and just throw. This is considered a catastrophic
            // event and the system should probably just shut down anyways.
            var msgList = new List <ButtplugMessage>();

            while (true)
            {
                try
                {
                    if (!reader.Read())
                    {
                        break;
                    }
                }
                catch (JsonReaderException e)
                {
                    throw new ButtplugMessageException(_bpLogger, $"Not valid JSON: {aJsonMsg} - {e.Message}");
                }

                JArray msgArray;
                try
                {
                    msgArray = JArray.Load(reader);
                }
                catch (JsonReaderException e)
                {
                    throw new ButtplugMessageException(_bpLogger, $"Not valid JSON: {aJsonMsg} - {e.Message}");
                }

                var errors = _schema.Validate(msgArray);
                if (errors.Any())
                {
                    throw new ButtplugMessageException(_bpLogger,
                                                       "Message does not conform to schema: " + string.Join(", ",
                                                                                                            errors.Select(aErr => aErr?.ToString()).ToArray()));
                }

                foreach (var jsonObj in msgArray.Children <JObject>())
                {
                    var msgName = jsonObj.Properties().First().Name;

                    // Only way we should get here is if the schema includes a class that we don't
                    // have a matching C# class for.
                    if (!_messageTypes.ContainsKey(msgName))
                    {
                        throw new ButtplugMessageException(_bpLogger, $"{msgName} is not a valid message class");
                    }

                    // This specifically could fail due to object conversion.
                    msgList.Add(DeserializeAs(jsonObj, _messageTypes[msgName]));
                }
            }

            return(msgList);
        }
 protected DeviceSubtypeManager([NotNull] IButtplugLogManager aLogManager)
 {
     LogManager = aLogManager;
     BpLogger   = aLogManager.GetLogger(GetType());
     BpLogger.Trace($"Setting up Device Manager {GetType().Name}");
 }
        public void Connect(Uri aURL, bool aIgnoreSSLErrors = false)
        {
            if (_ws != null && (_ws.State == WebSocketState.Connecting || _ws.State == WebSocketState.Open))
            {
                throw new InvalidOperationException("Already connected!");
            }

            _bpLogger.Trace("Opening connection");
            _ws = new WebSocket(aURL.ToString());
            _waitingMsgs.Clear();
            _devices.Clear();
            _counter = 1;

            _connectedOrFailed = new TaskCompletionSource <bool>();
            _disconnected      = new TaskCompletionSource <bool>();

            _ws.Opened += OpenedHandler;
            _ws.Closed += ClosedHandler;
            _ws.Error  += ErrorHandler;

            if (aIgnoreSSLErrors)
            {
                _ws.Security.AllowNameMismatchCertificate = true;
                _ws.Security.AllowUnstrustedCertificate   = true;
                _ws.Security.AllowCertificateChainErrors  = true;
            }

            _ws.Open();

            _connecting = true;
            _connectedOrFailed.Task.Wait();
            _connecting = false;

            if (_ws.State != WebSocketState.Open)
            {
                throw new Exception("Connection failed!");
            }
            _bpLogger.Trace("Connected");

            _ws.MessageReceived += MessageReceivedHandler;

            var res = SendMessage(new RequestServerInfo(_clientName)).Result;

            switch (res)
            {
            case ServerInfo si:
                if (si.MaxPingTime > 0)
                {
                    _pingTimer = new Timer(OnPingTimer, null, 0, Convert.ToInt32(Math.Round(((double)si.MaxPingTime) / 2, 0)));
                }

                if (si.MessageVersion < 1)
                {
                    throw new Exception("B******g Server's schema version (" + si.MessageVersion +
                                        ") is less than the client's (" + 1 +
                                        "). A newer server is required.");
                }

                // Get full device list and populate internal list
                var resp = SendMessage(new RequestDeviceList()).Result;
                if ((resp as DeviceList)?.Devices == null)
                {
                    if (resp is Error)
                    {
                        _owningDispatcher.Send(_ =>
                        {
                            ErrorReceived?.Invoke(this, new ErrorEventArgs(resp as Error));
                        }, null);
                    }

                    return;
                }

                foreach (var d in (resp as DeviceList).Devices)
                {
                    if (_devices.ContainsKey(d.DeviceIndex))
                    {
                        continue;
                    }

                    var device = new ButtplugClientDevice(d);
                    if (_devices.TryAdd(d.DeviceIndex, device))
                    {
                        _owningDispatcher.Send(_ =>
                        {
                            DeviceAdded?.Invoke(this, new DeviceEventArgs(device, DeviceEventArgs.DeviceAction.ADDED));
                        }, null);
                    }
                }

                break;

            case Error e:
                throw new Exception(e.ErrorMessage);

            default:
                throw new Exception("Unexpecte message returned: " + res.GetType());
            }
        }
        public ButtplugMessage[] Deserialize(string aJsonMsg)
        {
            _bpLogger?.Trace($"Got JSON Message: {aJsonMsg}");

            var    res = new List <ButtplugMessage>();
            JArray msgArray;

            try
            {
                msgArray = JArray.Parse(aJsonMsg);
            }
            catch (JsonReaderException e)
            {
                var err = new Error($"Not valid JSON: {aJsonMsg} - {e.Message}", ErrorClass.ERROR_MSG, ButtplugConsts.SystemMsgId);
                _bpLogger?.LogErrorMsg(err);
                res.Add(err);
                return(res.ToArray());
            }

            var errors = _schema.Validate(msgArray);

            if (errors.Any())
            {
                var err = new Error("Message does not conform to schema: " + string.Join(", ", errors.Select(aErr => aErr.ToString()).ToArray()), ErrorClass.ERROR_MSG, ButtplugConsts.SystemMsgId);
                _bpLogger?.LogErrorMsg(err);
                res.Add(err);
                return(res.ToArray());
            }

            if (!msgArray.Any())
            {
                var err = new Error("No messages in array", ErrorClass.ERROR_MSG, ButtplugConsts.SystemMsgId);
                _bpLogger?.LogErrorMsg(err);
                res.Add(err);
                return(res.ToArray());
            }

            // JSON input is an array of messages.
            // We currently only handle the first one.
            foreach (var o in msgArray.Children <JObject>())
            {
                if (!o.Properties().Any())
                {
                    var err = new Error("No message name available", ErrorClass.ERROR_MSG, ButtplugConsts.SystemMsgId);
                    _bpLogger?.LogErrorMsg(err);
                    res.Add(err);
                    continue;
                }

                var msgName = o.Properties().First().Name;
                if (!_messageTypes.Any() || !_messageTypes.ContainsKey(msgName))
                {
                    var err = new Error($"{msgName} is not a valid message class", ErrorClass.ERROR_MSG, ButtplugConsts.SystemMsgId);
                    _bpLogger?.LogErrorMsg(err);
                    res.Add(err);
                    continue;
                }

                // This specifically could fail due to object conversion.
                res.Add(DeserializeAs(o, _messageTypes[msgName], msgName, aJsonMsg));
            }

            return(res.ToArray());
        }
        public ButtplugMessage[] Deserialize(string aJsonMsg)
        {
            _bpLogger.Trace($"Got JSON Message: {aJsonMsg}");

            var    res = new List <ButtplugMessage>();
            JArray msgArray;

            try
            {
                msgArray = JArray.Parse(aJsonMsg);
            }
            catch (JsonReaderException e)
            {
                res.Add(_bpLogger.LogErrorMsg(ButtplugConsts.SystemMsgId, ErrorClass.ERROR_MSG, $"Not valid JSON: {aJsonMsg} - {e.Message}"));
                return(res.ToArray());
            }

            var errors = _schema.Validate(msgArray);

            if (errors.Any())
            {
                res.Add(_bpLogger.LogErrorMsg(ButtplugConsts.SystemMsgId, ErrorClass.ERROR_MSG, "Message does not conform to schema: " + string.Join(", ", errors.Select(aErr => aErr.ToString()).ToArray())));
                return(res.ToArray());
            }

            if (!msgArray.Any())
            {
                res.Add(_bpLogger.LogErrorMsg(ButtplugConsts.SystemMsgId, ErrorClass.ERROR_MSG, "No messages in array"));
                return(res.ToArray());
            }

            // JSON input is an array of messages.
            // We currently only handle the first one.
            foreach (var o in msgArray.Children <JObject>())
            {
                if (!o.Properties().Any())
                {
                    res.Add(_bpLogger.LogErrorMsg(ButtplugConsts.SystemMsgId, ErrorClass.ERROR_MSG, "No message name available"));
                    continue;
                }

                var msgName = o.Properties().First().Name;
                if (!_messageTypes.Keys.Any() || !_messageTypes.Keys.Contains(msgName))
                {
                    res.Add(_bpLogger.LogErrorMsg(ButtplugConsts.SystemMsgId, ErrorClass.ERROR_MSG, $"{msgName} is not a valid message class"));
                    continue;
                }

                var s = new JsonSerializer {
                    MissingMemberHandling = MissingMemberHandling.Error
                };

                // This specifically could fail due to object conversion.
                try
                {
                    var r = o[msgName].Value <JObject>();
                    res.Add((ButtplugMessage)r.ToObject(_messageTypes[msgName], s));
                    _bpLogger.Trace($"Message successfully parsed as {msgName} type");
                }
                catch (InvalidCastException e)
                {
                    res.Add(_bpLogger.LogErrorMsg(ButtplugConsts.SystemMsgId, ErrorClass.ERROR_MSG, $"Could not create message for JSON {aJsonMsg}: {e.Message}"));
                }
                catch (JsonSerializationException e)
                {
                    res.Add(_bpLogger.LogErrorMsg(ButtplugConsts.SystemMsgId, ErrorClass.ERROR_MSG, $"Could not create message for JSON {aJsonMsg}: {e.Message}"));
                }
            }

            return(res.ToArray());
        }