Esempio n. 1
0
        private void DeviceRemovedHandler(object aObj, EventArgs aEvent)
        {
            if ((aObj as ButtplugDevice) == null)
            {
                _bpLogger.Error("Got DeviceRemoved message from an object that is not a ButtplugDevice.");
                return;
            }

            var device = (ButtplugDevice)aObj;

            // The device itself will fire the remove event, so look it up in the dictionary and translate that for clients.
            var entry = (from x in _devices where x.Value.Identifier == device.Identifier select x).ToList();

            if (!entry.Any())
            {
                _bpLogger.Error("Got DeviceRemoved Event from object that is not in devices dictionary");
            }

            if (entry.Count > 1)
            {
                _bpLogger.Error("Device being removed has multiple entries in device dictionary.");
            }

            foreach (var pair in entry.ToList())
            {
                pair.Value.DeviceRemoved -= DeviceRemovedHandler;
                _bpLogger.Info($"Device removed: {pair.Key} - {pair.Value.Name}");
                DeviceMessageReceived?.Invoke(this, new MessageReceivedEventArgs(new DeviceRemoved(pair.Key)));
            }
        }
Esempio n. 2
0
        public UWPBluetoothDeviceInterface(
            [NotNull] IButtplugLogManager aLogManager,
            [NotNull] IBluetoothDeviceInfo aInfo,
            [NotNull] BluetoothLEDevice aDevice,
            [NotNull] GattCharacteristic[] aChars)
        {
            _bpLogger  = aLogManager.GetLogger(GetType());
            _bleDevice = aDevice;

            if (aInfo.Characteristics.Count > 0)
            {
                foreach (var item in aInfo.Characteristics)
                {
                    var c = (from x in aChars
                             where x.Uuid == item.Value
                             select x).ToArray();
                    if (c.Length != 1)
                    {
                        var err = $"Cannot find characteristic ${item.Value} for device {Name}";
                        _bpLogger.Error(err);
                        throw new Exception(err);
                    }

                    if (_indexedChars == null)
                    {
                        _indexedChars = new Dictionary <uint, GattCharacteristic>();
                    }

                    _indexedChars.Add(item.Key, c[0]);
                }
            }
            else
            {
                foreach (var c in aChars)
                {
                    if (c.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Read) ||
                        c.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify) ||
                        c.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Indicate))
                    {
                        _rxChar = c;
                    }
                    else if (c.CharacteristicProperties.HasFlag(GattCharacteristicProperties.WriteWithoutResponse) ||
                             c.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Write))
                    {
                        _txChar = c;
                    }
                }
            }

            if (_rxChar == null && _txChar == null && _indexedChars == null)
            {
                var err = $"No characteristics to connect to for device {Name}";
                _bpLogger.Error(err);
                throw new Exception(err);
            }

            _bleDevice.ConnectionStatusChanged += ConnectionStatusChangedHandler;
        }
Esempio n. 3
0
        public async Task <IButtplugDevice> CreateDeviceAsync([NotNull] BluetoothLEDevice aDevice)
        {
            // GetGattServicesForUuidAsync is 15063 only
            var services = await aDevice.GetGattServicesAsync(BluetoothCacheMode.Cached);

            List <Guid> uuids = new List <Guid>();

            foreach (var s in services.Services)
            {
                _bpLogger.Trace($"Found service UUID: {s.Uuid} ({aDevice.Name})");
                uuids.Add(s.Uuid);
            }

            var srvs = (from x in services.Services
                        from y in _deviceInfo.Services
                        where x.Uuid == y
                        select x).ToArray();

            if (srvs.Length != 1)
            {
                // Somehow we've gotten multiple services back, something we don't currently support.
                _bpLogger.Error($"Found {srvs.Length} services for {aDevice.Name}, which is more/less than 1. Please fix this in the bluetooth definition.");
                return(null);
            }

            var service = srvs[0];

            var chrResult = await service.GetCharacteristicsAsync();

            if (chrResult.Status != GattCommunicationStatus.Success)
            {
                _bpLogger.Error($"Cannot connect to service {service.Uuid} of {aDevice.Name}.");
                return(null);
            }

            foreach (var s in chrResult.Characteristics)
            {
                _bpLogger.Trace($"Found characteristics UUID: {s.Uuid} ({aDevice.Name})");
            }

            var chrs = chrResult.Characteristics.ToArray();

            // If there aren't any characteristics by this point, something has gone wrong.
            if (!chrs.Any())
            {
                _bpLogger.Error($"Cannot find characteristics for service {service.Uuid} of {aDevice.Name}.");
                return(null);
            }

            var bleInterface = new UWPBluetoothDeviceInterface(_buttplugLogManager, _deviceInfo, aDevice, chrs);

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

            // If initialization fails, don't actually send the message back. Just return null, we'll
            // have the info in the logs.
            return(await device.InitializeAsync().ConfigureAwait(false) is Ok ? device : null);
        }
Esempio n. 4
0
        public async Task ShutdownAsync(CancellationToken aToken = default(CancellationToken))
        {
            // Don't disconnect devices on shutdown, as they won't actually close.
            // Uncomment this once we figure out how to close bluetooth devices.
            // _deviceManager.RemoveAllDevices();
            var msg = await _deviceManager.SendMessageAsync(new StopAllDevices(), aToken).ConfigureAwait(false);

            if (msg is Error error)
            {
                _bpLogger.Error("An error occured while stopping devices on shutdown.");
                _bpLogger.Error(error.ErrorMessage);
            }

            _deviceManager.StopScanning();
            _deviceManager.DeviceMessageReceived -= DeviceMessageReceivedHandler;
            _deviceManager.ScanningFinished      -= ScanningFinishedHandler;
            BpLogManager.LogMessageReceived      -= LogMessageReceivedHandler;
        }
        public async Task Shutdown()
        {
            // Don't disconnect devices on shutdown, as they won't actually close.
            // Uncomment this once we figure out how to close bluetooth devices.
            // _deviceManager.RemoveAllDevices();
            var msg = await _deviceManager.SendMessage(new StopAllDevices());

            if (msg is Error)
            {
                _bpLogger.Error("An error occured while stopping devices on shutdown.");
                _bpLogger.Error((msg as Error).ErrorMessage);
            }

            _deviceManager.StopScanning();
            _deviceManager.DeviceMessageReceived -= DeviceMessageReceivedHandler;
            _deviceManager.ScanningFinished      -= ScanningFinishedHandler;
            BpLogManager.LogMessageReceived      -= LogMessageReceivedHandler;
        }
        public async Task SubscribeToUpdates()
        {
            if (_rxChar != null && _rxChar.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify))
            {
                GattCommunicationStatus status = await _rxChar.WriteClientCharacteristicConfigurationDescriptorAsync(
                    GattClientCharacteristicConfigurationDescriptorValue.Notify);

                if (status == GattCommunicationStatus.Success)
                {
                    // Server has been informed of clients interest.
                    _rxChar.ValueChanged += BluetoothNotifyReceivedHandler;
                }
                else
                {
                    _bpLogger.Error($"Cannot subscribe to BLE updates from {Name}: Failed Request");
                }
            }
            else
            {
                _bpLogger.Error($"Cannot subscribe to BLE updates from {Name}: No Rx characteristic found.");
            }
        }
Esempio n. 7
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));
        }
Esempio n. 8
0
        public async Task SubscribeToUpdatesAsync(uint aIndex)
        {
            if (_indexedChars == null)
            {
                _bpLogger.Error("SubscribeToUpdates using indexed characteristics called with no indexed characteristics available");
                return;
            }

            if (!_indexedChars.ContainsKey(aIndex))
            {
                _bpLogger.Error("SubscribeToUpdates using indexed characteristics called with invalid index");
                return;
            }

            await SubscribeToUpdatesAsync(_indexedChars[aIndex]).ConfigureAwait(false);
        }
        /// <summary>
        /// Serializes a single <see cref="ButtplugMessage"/> object into a JSON string for a specified version of the schema.
        /// </summary>
        /// <param name="aMsg"><see cref="ButtplugMessage"/> object</param>
        /// <param name="clientSchemaVersion">Target schema version</param>
        /// <returns>JSON string representing a B******g message</returns>
        public string Serialize([NotNull] ButtplugMessage aMsg, uint clientSchemaVersion)
        {
            // Warning: Any log messages in this function must be localOnly. They will possibly recurse.

            // Support downgrading messages
            var tmp = aMsg;

            while (tmp == null || tmp.SchemaVersion > clientSchemaVersion)
            {
                if (tmp?.PreviousType == null)
                {
                    if (aMsg.Id == ButtplugConsts.SystemMsgId)
                    {
                        // There's no previous version of this system message
                        _bpLogger?.Warn($"No messages serialized!");
                        return(null);
                    }

                    var err = new Error($"No backwards compatible version for message #{aMsg.GetType().Name}!",
                                        ErrorClass.ERROR_MSG, aMsg.Id);
                    var eo = new JObject(new JProperty(err.GetType().Name, JObject.FromObject(err)));
                    var ea = new JArray(eo);
                    _bpLogger?.Error(err.ErrorMessage, true);
                    _bpLogger?.Trace($"Message serialized to: {ea.ToString(Formatting.None)}", true);
                    return(ea.ToString(Formatting.None));
                }

                tmp = (ButtplugMessage)aMsg.PreviousType.GetConstructor(
                    new Type[] { tmp.GetType() })?.Invoke(new object[] { tmp });
            }

            var o = new JObject(new JProperty(aMsg.GetType().Name, JObject.FromObject(tmp)));
            var a = new JArray(o);

            _bpLogger?.Trace($"Message serialized to: {a.ToString(Formatting.None)}", true);
            return(a.ToString(Formatting.None));
        }
        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));
            }
        }
Esempio n. 12
0
 /// <inheritdoc />
 /// <summary>
 /// Creates a ButtplugException.
 /// </summary>
 /// <param name="aLogger">Logger to log exception error message through (gives type context for the message).</param>
 /// <param name="aMessage">Exception message.</param>
 /// <param name="aClass">Exception class, based on B******g Error Message Classes. (https://b******g-spec.docs.b******g.io/status.html#error)</param>
 /// <param name="aId">Message ID for the resulting B******g Error Message.</param>
 /// <param name="aInner">Optional inner exception.</param>
 public ButtplugException([NotNull] IButtplugLog aLogger, string aMessage, Error.ErrorClass aClass = Error.ErrorClass.ERROR_UNKNOWN, uint aId = ButtplugConsts.SystemMsgId, Exception aInner = null)
     : this(aMessage, aClass, aId, aInner)
 {
     ButtplugUtils.ArgumentNotNull(aLogger, nameof(aLogger));
     aLogger.Error(aMessage);
 }
Esempio n. 13
0
 public static ButtplugException FromError(IButtplugLog aLogger, Error aMsg)
 {
     ButtplugUtils.ArgumentNotNull(aLogger, nameof(aLogger));
     aLogger.Error(aMsg.ErrorMessage);
     return(FromError(aMsg));
 }
Esempio n. 14
0
 /// <summary>
 /// Logs an <see cref="Error"/> B******g message as an error level log message.
 /// </summary>
 /// <param name="logger">Logger to use for message</param>
 /// <param name="error">Error B******g message</param>
 public static void LogErrorMsg(this IButtplugLog logger, Error error)
 {
     logger.Error(error.ErrorMessage);
 }
Esempio n. 15
0
        public async Task RunServerSession()
        {
            try
            {
                var readTask  = _ws.ReadStringAsync(_linkedCancelSource.Token);
                var writeTask = _outgoingMessages.OutputAvailableAsync(_linkedCancelSource.Token);
                while (_ws.IsConnected && !_linkedCancelSource.IsCancellationRequested)
                {
                    var msgTasks = new Task[]
                    {
                        readTask,
                        writeTask,
                    };

                    var completedTaskIndex = Task.WaitAny(msgTasks);

                    if (completedTaskIndex == 0)
                    {
                        var incomingMsg = await((Task <string>)msgTasks[0]).ConfigureAwait(false);
                        if (incomingMsg != null)
                        {
                            ButtplugMessage[] msg;
                            try
                            {
                                msg = await _server.SendMessageAsync(incomingMsg).ConfigureAwait(false);
                            }
                            catch (ButtplugException ex)
                            {
                                msg = new ButtplugMessage[] { ex.ButtplugErrorMessage };
                            }
                            await QueueMessage(msg).ConfigureAwait(false);
                        }

                        readTask = _ws.ReadStringAsync(_linkedCancelSource.Token);
                    }
                    else
                    {
                        try
                        {
                            _outgoingMessages.TryReceiveAll(out var msgs);
                            var outMsgs = msgs.Aggregate(string.Empty, (current, msg) => current + msg);
                            if (_ws != null && _ws.IsConnected)
                            {
                                await _ws.WriteStringAsync(outMsgs, _linkedCancelSource.Token).ConfigureAwait(false);
                            }

                            writeTask = _outgoingMessages.OutputAvailableAsync(_linkedCancelSource.Token);
                        }
                        catch (WebSocketException e)
                        {
                            // Probably means we're replying to a message we received just before shutdown.
                            _logger.Error(e.Message, true);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                _logger.Error(e.Message, true);
            }
            finally
            {
                await ShutdownSession().ConfigureAwait(false);
            }
        }
Esempio n. 16
0
        private async Task ConnectionAccepter(string aPipeName, CancellationToken aToken)
        {
            while (!aToken.IsCancellationRequested)
            {
                var pipeServer = new NamedPipeServerStream(aPipeName, PipeDirection.InOut, 10, PipeTransmissionMode.Message, PipeOptions.Asynchronous);
                await pipeServer.WaitForConnectionAsync(aToken).ConfigureAwait(false);

                if (!pipeServer.IsConnected)
                {
                    continue;
                }

                var aServer = pipeServer;
                ConnectionAccepted?.Invoke(this, new IPCConnectionEventArgs());

                var buttplugServer = _serverFactory();

                void MsgReceived(object aObject, MessageReceivedEventArgs aEvent)
                {
                    var msg = buttplugServer.Serialize(aEvent.Message);

                    if (msg == null)
                    {
                        return;
                    }

                    try
                    {
                        if (aServer != null && aServer.IsConnected)
                        {
                            var output = Encoding.UTF8.GetBytes(msg);
                            aServer.WriteAsync(output, 0, output.Length, aToken);
                        }

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

                buttplugServer.MessageReceived += MsgReceived;

                void ClientConnected(object aObject, EventArgs aUnused)
                {
                    ConnectionUpdated?.Invoke(this, new IPCConnectionEventArgs(buttplugServer.ClientName));
                }

                buttplugServer.ClientConnected += ClientConnected;

                try
                {
                    _connections.Enqueue(aServer);
                    while (!aToken.IsCancellationRequested && aServer.IsConnected)
                    {
                        var buffer = new byte[4096];
                        var msg    = string.Empty;
                        var len    = -1;
                        while (len < 0 || (len == buffer.Length && buffer[4095] != '\0'))
                        {
                            try
                            {
                                len = await aServer.ReadAsync(buffer, 0, buffer.Length, aToken).ConfigureAwait(false);

                                if (len > 0)
                                {
                                    msg += Encoding.UTF8.GetString(buffer, 0, len);
                                }
                            }
                            catch
                            {
                                // no-op?
                            }
                        }

                        if (msg.Length > 0)
                        {
                            ButtplugMessage[] respMsgs;
                            try
                            {
                                respMsgs = await buttplugServer.SendMessageAsync(msg).ConfigureAwait(false);
                            }
                            catch (ButtplugException e)
                            {
                                respMsgs = new ButtplugMessage[] { e.ButtplugErrorMessage };
                            }

                            var respMsg = buttplugServer.Serialize(respMsgs);
                            if (respMsg == null)
                            {
                                continue;
                            }

                            var output = Encoding.UTF8.GetBytes(respMsg);
                            await aServer.WriteAsync(output, 0, output.Length, aToken).ConfigureAwait(false);

                            foreach (var m in respMsgs)
                            {
                                if (m is Error && (m as Error).ErrorCode == Error.ErrorClass.ERROR_PING && aServer.IsConnected)
                                {
                                    aServer.Close();
                                }
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    _logger.Error(e.Message, true);
                    try
                    {
                        aServer.Close();
                    }
                    catch
                    {
                        // noop
                    }
                }
                finally
                {
                    buttplugServer.MessageReceived -= MsgReceived;
                    await buttplugServer.ShutdownAsync().ConfigureAwait(false);

                    buttplugServer = null;
                    _connections.TryDequeue(out var stashed);
                    while (stashed != aServer && _connections.Any())
                    {
                        _connections.Enqueue(stashed);
                        _connections.TryDequeue(out stashed);
                    }

                    aServer.Close();
                    aServer.Dispose();
                    aServer = null;
                    ConnectionClosed?.Invoke(this, new IPCConnectionEventArgs());
                }
            }
        }
Esempio n. 17
0
 public static Error LogErrorMsg(this IButtplugLog logger, uint aId, Error.ErrorClass aCode, string aMsg)
 {
     logger.Error(aMsg);
     return(new Error(aMsg, aCode, aId));
 }