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));
        }
Exemple #2
0
        public async Task <ButtplugMessage> SendMessage(ButtplugMessage aMsg)
        {
            var id = aMsg.Id;

            switch (aMsg)
            {
            case StartScanning _:
                StartScanning();
                return(new Ok(id));

            case StopScanning _:
                StopScanning();
                return(new Ok(id));

            case StopAllDevices _:
                var isOk     = true;
                var errorMsg = string.Empty;
                foreach (var d in _devices.ToList())
                {
                    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 _:
                var msgDevices = _devices
                                 .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."));
        }
        public async Task <ButtplugMessage> WriteValue(uint aMsgId, byte[] aValue, bool aWriteWithResponse = false)
        {
            if (_txChar == null)
            {
                return(_bpLogger.LogErrorMsg(aMsgId, Error.ErrorClass.ERROR_DEVICE,
                                             $"WriteValue using txChar called with no txChar available"));
            }

            return(await WriteValue(aMsgId, _txChar, aValue, aWriteWithResponse));
        }
        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));
        }
Exemple #5
0
        public async Task <ButtplugMessage> WriteValue(uint aMsgId,
                                                       Guid aCharacteristic,
                                                       byte[] aValue,
                                                       bool aWriteOption = false)
        {
            if (!(_currentTask is null))
            {
                _currentTask.Cancel();
                _bpLogger.Error("Cancelling device transfer in progress for new transfer.");
            }

            var chrs = from x in _gattCharacteristics
                       where x.Uuid == aCharacteristic
                       select x;

            var gattCharacteristics = chrs.ToArray();

            if (!gattCharacteristics.Any())
            {
                return(_bpLogger.LogErrorMsg(aMsgId, Error.ErrorClass.ERROR_DEVICE, $"Requested characteristic {aCharacteristic} not found"));
            }
            else if (gattCharacteristics.Length > 1)
            {
                _bpLogger.Warn($"Multiple gattCharacteristics for {aCharacteristic} found");
            }

            var gattCharacteristic = gattCharacteristics[0];

            _currentTask = gattCharacteristic.WriteValueAsync(aValue.AsBuffer(), aWriteOption ? GattWriteOption.WriteWithResponse : GattWriteOption.WriteWithoutResponse);
            try
            {
                var status = await _currentTask;
                _currentTask = null;
                if (status != GattCommunicationStatus.Success)
                {
                    return(_bpLogger.LogErrorMsg(aMsgId, Error.ErrorClass.ERROR_DEVICE, $"GattCommunication Error: {status}"));
                }
            }
            catch (InvalidOperationException e)
            {
                // This exception will be thrown if the bluetooth device disconnects in the middle of a transfer.
                return(_bpLogger.LogErrorMsg(aMsgId, Error.ErrorClass.ERROR_DEVICE, $"GattCommunication Error: {e.Message}"));
            }

            return(new Ok(aMsgId));
        }
        /// <inheritdoc />
        public async Task <ButtplugMessage> ParseMessage([NotNull] ButtplugDeviceMessage aMsg)
        {
            if (_isDisconnected)
            {
                return(BpLogger.LogErrorMsg(aMsg.Id, ErrorClass.ERROR_DEVICE,
                                            $"{Name} has disconnected and can no longer process messages."));
            }

            if (!MsgFuncs.ContainsKey(aMsg.GetType()))
            {
                return(BpLogger.LogErrorMsg(aMsg.Id, ErrorClass.ERROR_DEVICE,
                                            $"{Name} cannot handle message of type {aMsg.GetType().Name}"));
            }

            // We just checked whether the key exists above, so we're ok.
            // ReSharper disable once PossibleNullReferenceException
            return(await MsgFuncs[aMsg.GetType()].Function.Invoke(aMsg));
        }
        public async Task <ButtplugMessage> WriteValue(uint aMsgId,
                                                       uint aCharacteristicIndex,
                                                       byte[] aValue,
                                                       bool aWriteOption = false)
        {
            if (!(_currentTask is null))
            {
                _currentTask.Cancel();
                _bpLogger.Error("Cancelling device transfer in progress for new transfer.");
            }

            var gattCharacteristic = _gattCharacteristics[aCharacteristicIndex];

            if (gattCharacteristic == null)
            {
                return(_bpLogger.LogErrorMsg(aMsgId, Error.ErrorClass.ERROR_DEVICE, $"Requested character {aCharacteristicIndex} out of range"));
            }

            _currentTask = gattCharacteristic.WriteValueAsync(aValue.AsBuffer(), aWriteOption ? GattWriteOption.WriteWithResponse : GattWriteOption.WriteWithoutResponse);
            try
            {
                var status = await _currentTask;
                _currentTask = null;
                if (status != GattCommunicationStatus.Success)
                {
                    return(_bpLogger.LogErrorMsg(aMsgId, Error.ErrorClass.ERROR_DEVICE, $"GattCommunication Error: {status}"));
                }
            }
            catch (InvalidOperationException e)
            {
                // This exception will be thrown if the bluetooth device disconnects in the middle of a transfer.
                return(_bpLogger.LogErrorMsg(aMsgId, Error.ErrorClass.ERROR_DEVICE, $"GattCommunication Error: {e.Message}"));
            }

            return(new Ok(aMsgId));
        }
        private async Task HandleConnectionAsync(WebSocket ws, CancellationToken cancellation)
        {
            if (!_connections.IsEmpty)
            {
                try
                {
                    ws.WriteString(new ButtplugJsonMessageParser(_logManager).Serialize(_logger.LogErrorMsg(
                                                                                            ButtplugConsts.SystemMsgId, ErrorClass.ERROR_INIT, "WebSocketServer already in use!"), 0));
                    ws.Close();
                }
                catch
                {
                    // noop
                }
                finally
                {
                    ws.Dispose();
                }

                return;
            }

            var remoteId = ws.RemoteEndpoint.ToString();

            _connections.AddOrUpdate(remoteId, ws, (oldWs, newWs) => newWs);
            ConnectionAccepted?.Invoke(this, new ConnectionEventArgs(remoteId));

            var b******g = _factory.GetServer();

            EventHandler <MessageReceivedEventArgs> msgReceived = (aObject, aEvent) =>
            {
                var msg = b******g.Serialize(aEvent.Message);
                if (msg == null)
                {
                    return;
                }

                try
                {
                    if (ws != null && ws.IsConnected)
                    {
                        ws.WriteString(msg);
                    }

                    if (aEvent.Message is Error && (aEvent.Message as Error).ErrorCode == Error.ErrorClass.ERROR_PING && ws != null && ws.IsConnected)
                    {
                        ws.Close();
                    }
                }
                catch (WebSocketException e)
                {
                    // Probably means we're repling to a message we recieved just before shutdown.
                    _logger.Error(e.Message, true);
                }
            };

            b******g.MessageReceived += msgReceived;

            EventHandler <MessageReceivedEventArgs> clientConnected = (aObject, aEvent) =>
            {
                var msg        = aEvent.Message as RequestServerInfo;
                var clientName = msg?.ClientName ?? "Unknown client";
                ConnectionUpdated?.Invoke(this, new ConnectionEventArgs(remoteId, clientName));
            };

            b******g.ClientConnected += clientConnected;

            try
            {
                while (ws.IsConnected && !cancellation.IsCancellationRequested)
                {
                    var msg = await ws.ReadStringAsync(cancellation).ConfigureAwait(false);

                    if (msg != null)
                    {
                        var respMsgs = await b******g.SendMessage(msg);

                        var respMsg = b******g.Serialize(respMsgs);
                        if (respMsg == null)
                        {
                            continue;
                        }

                        await ws.WriteStringAsync(respMsg, cancellation);

                        foreach (var m in respMsgs)
                        {
                            if (m is Error && (m as Error).ErrorCode == Error.ErrorClass.ERROR_PING && ws != null && ws.IsConnected)
                            {
                                ws.Close();
                            }
                        }
                    }
                }
            }
            catch (Exception e)
            {
                _logger.Error(e.Message, true);
                try
                {
                    ws.Close();
                }
                catch
                {
                    // noop
                }
            }
            finally
            {
                b******g.MessageReceived -= msgReceived;
                await b******g.Shutdown();

                b******g = null;
                ws.Dispose();
                _connections.TryRemove(remoteId, out _);
                ConnectionClosed?.Invoke(this, new ConnectionEventArgs(remoteId));
            }
        }
        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());
        }