/// <summary>
        /// Consume a packet from the buffer if possible, then advance the buffer index.
        /// </summary>
        /// <param name="buffer">Byte buffer to decode</param>
        /// <param name="index">(Ref) Index to read into buffer</param>
        /// <exception cref="WearableProxyProtocolException">Thrown when a packet cannot be decoded and the buffer
        /// must be discarded.</exception>
        /// <exception cref="IndexOutOfRangeException">Thrown when a packet was partially consumed but ran out of
        /// buffer contents.</exception>
        public override void ProcessPacket(byte[] buffer, ref int index)
        {
            PacketTypeCode packetType = DecodePacketType(buffer, ref index);

            switch (packetType)
            {
            case PacketTypeCode.KeepAlive:
            {
                CheckFooter(buffer, ref index);

                if (KeepAlive != null)
                {
                    KeepAlive.Invoke();
                }

                break;
            }

            case PacketTypeCode.PingQuery:
            {
                CheckFooter(buffer, ref index);

                if (PingQuery != null)
                {
                    PingQuery.Invoke();
                }

                break;
            }

            case PacketTypeCode.PingResponse:
            {
                CheckFooter(buffer, ref index);

                if (PingResponse != null)
                {
                    PingResponse.Invoke();
                }

                break;
            }

            case PacketTypeCode.SensorControl:
            {
                bool     enabled;
                SensorId sensor = DecodeSensorControl(buffer, ref index, out enabled);
                CheckFooter(buffer, ref index);

                if (SensorControl != null)
                {
                    SensorControl.Invoke(sensor, enabled);
                }

                break;
            }

            case PacketTypeCode.SetRssiFilter:
            {
                int value = DecodeRSSIFilterControlPacket(buffer, ref index);
                CheckFooter(buffer, ref index);

                if (RSSIFilterValueChange != null)
                {
                    RSSIFilterValueChange.Invoke(value);
                }

                break;
            }

            case PacketTypeCode.InitiateDeviceSearch:
            {
                CheckFooter(buffer, ref index);

                if (InitiateDeviceSearch != null)
                {
                    InitiateDeviceSearch.Invoke();
                }

                break;
            }

            case PacketTypeCode.StopDeviceSearch:
            {
                CheckFooter(buffer, ref index);

                if (StopDeviceSearch != null)
                {
                    StopDeviceSearch.Invoke();
                }

                break;
            }

            case PacketTypeCode.ConnectToDevice:
            {
                string uid = DecodeDeviceConnectPacket(buffer, ref index);
                CheckFooter(buffer, ref index);

                if (ConnectToDevice != null)
                {
                    ConnectToDevice.Invoke(uid);
                }

                break;
            }

            case PacketTypeCode.DisconnectFromDevice:
            {
                CheckFooter(buffer, ref index);

                if (DisconnectFromDevice != null)
                {
                    DisconnectFromDevice.Invoke();
                }

                break;
            }

            case PacketTypeCode.QueryConnectionStatus:
            {
                CheckFooter(buffer, ref index);

                if (QueryConnectionStatus != null)
                {
                    QueryConnectionStatus.Invoke();
                }

                break;
            }

            case PacketTypeCode.QueryUpdateInterval:
            {
                CheckFooter(buffer, ref index);

                if (QueryUpdateInterval != null)
                {
                    QueryUpdateInterval.Invoke();
                }

                break;
            }

            case PacketTypeCode.SetUpdateInterval:
            {
                SensorUpdateInterval interval = DecodeSetUpdateIntervalPacket(buffer, ref index);
                CheckFooter(buffer, ref index);

                if (SetUpdateInterval != null)
                {
                    SetUpdateInterval.Invoke(interval);
                }

                break;
            }

            case PacketTypeCode.QuerySensorStatus:
            {
                CheckFooter(buffer, ref index);

                if (QuerySensorStatus != null)
                {
                    QuerySensorStatus.Invoke();
                }

                break;
            }

            case PacketTypeCode.GestureControl:
            {
                bool      enabled;
                GestureId gestureId = DecodeGestureControl(buffer, ref index, out enabled);
                CheckFooter(buffer, ref index);

                if (GestureControl != null)
                {
                    GestureControl.Invoke(gestureId, enabled);
                }

                break;
            }

            case PacketTypeCode.QueryGestureStatus:
            {
                CheckFooter(buffer, ref index);

                if (QueryGestureStatus != null)
                {
                    QueryGestureStatus.Invoke();
                }

                break;
            }

            case PacketTypeCode.QueryRotationSource:
            {
                CheckFooter(buffer, ref index);

                if (QueryRotationSource != null)
                {
                    QueryRotationSource.Invoke();
                }

                break;
            }

            case PacketTypeCode.SetRotationSource:
            {
                RotationSensorSource source = DecodeRotationSource(buffer, ref index);
                CheckFooter(buffer, ref index);

                if (SetRotationSource != null)
                {
                    SetRotationSource.Invoke(source);
                }

                break;
            }

            case PacketTypeCode.SensorFrame:
            case PacketTypeCode.DeviceList:
            case PacketTypeCode.ConnectionStatus:
            case PacketTypeCode.SensorStatus:
            case PacketTypeCode.UpdateIntervalValue:
            case PacketTypeCode.GestureStatus:
            case PacketTypeCode.RotationSourceValue:
                // Known, but contextually-invalid packet
                throw new WearableProxyProtocolException(WearableConstants.ProxyProviderInvalidPacketError);

            default:
                // Unknown or corrupt packet
                throw new WearableProxyProtocolException(WearableConstants.ProxyProviderInvalidPacketError);
            }
        }
        /// <summary>
        /// Consume a packet from the buffer if possible, then advance the buffer index.
        /// </summary>
        /// <param name="buffer">Byte buffer to decode</param>
        /// <param name="index">(Ref) Index to read into buffer</param>
        /// <exception cref="WearableProxyProtocolException">Thrown when a packet cannot be decoded and the buffer
        /// must be discarded.</exception>
        /// <exception cref="IndexOutOfRangeException">Thrown when a packet was partially consumed but ran out of
        /// buffer contents.</exception>
        public override void ProcessPacket(byte[] buffer, ref int index)
        {
            PacketTypeCode packetType = DecodePacketType(buffer, ref index);

            switch (packetType)
            {
            case PacketTypeCode.KeepAlive:
            {
                CheckFooter(buffer, ref index);

                if (KeepAlive != null)
                {
                    KeepAlive.Invoke();
                }

                break;
            }

            case PacketTypeCode.PingQuery:
            {
                CheckFooter(buffer, ref index);

                if (PingQuery != null)
                {
                    PingQuery.Invoke();
                }

                break;
            }

            case PacketTypeCode.PingResponse:
            {
                CheckFooter(buffer, ref index);

                if (PingResponse != null)
                {
                    PingResponse.Invoke();
                }

                break;
            }

            case PacketTypeCode.SensorFrame:
            {
                SensorFrame frame = DecodeSensorFrame(buffer, ref index);
                CheckFooter(buffer, ref index);

                if (NewSensorFrame != null)
                {
                    NewSensorFrame.Invoke(frame);
                }

                break;
            }

            case PacketTypeCode.DeviceList:
            {
                Device[] devices = DecodeDeviceList(buffer, ref index);
                CheckFooter(buffer, ref index);

                if (DeviceList != null)
                {
                    DeviceList.Invoke(devices);
                }

                break;
            }

            case PacketTypeCode.ConnectionStatus:
            {
                Device?         device;
                ConnectionState status = DecodeConnectionStatus(buffer, ref index, out device);
                CheckFooter(buffer, ref index);

                if (ConnectionStatus != null)
                {
                    ConnectionStatus.Invoke(status, device);
                }

                break;
            }

            case PacketTypeCode.ConfigStatus:
            {
                WearableDeviceConfig config = DeserializeDeviceConfig(buffer, ref index);
                CheckFooter(buffer, ref index);

                if (ConfigStatus != null)
                {
                    ConfigStatus.Invoke(config);
                }
                break;
            }

            case PacketTypeCode.SetRssiFilter:
            case PacketTypeCode.InitiateDeviceSearch:
            case PacketTypeCode.StopDeviceSearch:
            case PacketTypeCode.ConnectToDevice:
            case PacketTypeCode.DisconnectFromDevice:
            case PacketTypeCode.QueryConnectionStatus:
            case PacketTypeCode.SetNewConfig:
            case PacketTypeCode.QueryConfig:
                // This is a known, but contextually-invalid packet type
                throw new WearableProxyProtocolException(WearableConstants.ProxyProviderInvalidPacketError);

            default:
                // This is an unknown or invalid packet type
                throw new WearableProxyProtocolException(WearableConstants.ProxyProviderInvalidPacketError);
            }
        }
        /// <summary>
        /// Consume a packet from the buffer if possible, then advance the buffer index.
        /// </summary>
        /// <param name="buffer">Byte buffer to decode</param>
        /// <param name="index">(Ref) Index to read into buffer</param>
        /// <exception cref="WearableProxyProtocolException">Thrown when a packet cannot be decoded and the buffer
        /// must be discarded.</exception>
        /// <exception cref="IndexOutOfRangeException">Thrown when a packet was partially consumed but ran out of
        /// buffer contents.</exception>
        public override void ProcessPacket(byte[] buffer, ref int index)
        {
            PacketTypeCode packetType = DecodePacketType(buffer, ref index);

            switch (packetType)
            {
            case PacketTypeCode.KeepAlive:
            {
                CheckFooter(buffer, ref index);

                if (KeepAlive != null)
                {
                    KeepAlive.Invoke();
                }

                break;
            }

            case PacketTypeCode.PingQuery:
            {
                CheckFooter(buffer, ref index);

                if (PingQuery != null)
                {
                    PingQuery.Invoke();
                }

                break;
            }

            case PacketTypeCode.PingResponse:
            {
                CheckFooter(buffer, ref index);

                if (PingResponse != null)
                {
                    PingResponse.Invoke();
                }

                break;
            }

            case PacketTypeCode.SensorFrame:
            {
                SensorFrame frame = DecodeSensorFrame(buffer, ref index);
                CheckFooter(buffer, ref index);

                if (NewSensorFrame != null)
                {
                    NewSensorFrame.Invoke(frame);
                }

                break;
            }

            case PacketTypeCode.DeviceList:
            {
                Device[] devices = DecodeDeviceList(buffer, ref index);
                CheckFooter(buffer, ref index);

                if (DeviceList != null)
                {
                    DeviceList.Invoke(devices);
                }

                break;
            }

            case PacketTypeCode.ConnectionStatus:
            {
                Device?         device;
                ConnectionState status = DecodeConnectionStatus(buffer, ref index, out device);
                CheckFooter(buffer, ref index);

                if (ConnectionStatus != null)
                {
                    ConnectionStatus.Invoke(status, device);
                }

                break;
            }

            case PacketTypeCode.SensorStatus:
            {
                bool     enabled;
                SensorId sensor = DecodeSensorStatus(buffer, ref index, out enabled);
                CheckFooter(buffer, ref index);

                if (SensorStatus != null)
                {
                    SensorStatus.Invoke(sensor, enabled);
                }

                break;
            }

            case PacketTypeCode.UpdateIntervalValue:
            {
                SensorUpdateInterval rate = DecodeUpdateInterval(buffer, ref index);
                CheckFooter(buffer, ref index);

                if (SensorUpdateIntervalValue != null)
                {
                    SensorUpdateIntervalValue.Invoke(rate);
                }

                break;
            }

            case PacketTypeCode.GestureStatus:
            {
                bool      enabled;
                GestureId gesture = DecodeGestureStatus(buffer, ref index, out enabled);
                CheckFooter(buffer, ref index);

                if (GestureStatus != null)
                {
                    GestureStatus.Invoke(gesture, enabled);
                }

                break;
            }

            case PacketTypeCode.RotationSourceValue:
            {
                RotationSensorSource source = DecodeRotationSource(buffer, ref index);
                CheckFooter(buffer, ref index);

                if (RotationSourceValue != null)
                {
                    RotationSourceValue.Invoke(source);
                }

                break;
            }

            case PacketTypeCode.SensorControl:
            case PacketTypeCode.SetRssiFilter:
            case PacketTypeCode.InitiateDeviceSearch:
            case PacketTypeCode.StopDeviceSearch:
            case PacketTypeCode.ConnectToDevice:
            case PacketTypeCode.DisconnectFromDevice:
            case PacketTypeCode.QueryConnectionStatus:
            case PacketTypeCode.QueryUpdateInterval:
            case PacketTypeCode.SetUpdateInterval:
            case PacketTypeCode.QuerySensorStatus:
            case PacketTypeCode.GestureControl:
            case PacketTypeCode.QueryRotationSource:
            case PacketTypeCode.SetRotationSource:
                // This is a known, but contextually-invalid packet type
                throw new WearableProxyProtocolException(WearableConstants.ProxyProviderInvalidPacketError);

            default:
                // This is an unknown or invalid packet type
                throw new WearableProxyProtocolException(WearableConstants.ProxyProviderInvalidPacketError);
            }
        }
        /// <summary>
        /// Consume a packet from the buffer if possible, then advance the buffer index.
        /// </summary>
        /// <param name="buffer">Byte buffer to decode</param>
        /// <param name="index">(Ref) Index to read into buffer</param>
        /// <exception cref="WearableProxyProtocolException">Thrown when a packet cannot be decoded and the buffer
        /// must be discarded.</exception>
        /// <exception cref="IndexOutOfRangeException">Thrown when a packet was partially consumed but ran out of
        /// buffer contents.</exception>
        public override void ProcessPacket(byte[] buffer, ref int index)
        {
            PacketTypeCode packetType = DecodePacketType(buffer, ref index);

            switch (packetType)
            {
            case PacketTypeCode.KeepAlive:
            {
                CheckFooter(buffer, ref index);

                if (KeepAlive != null)
                {
                    KeepAlive.Invoke();
                }

                break;
            }

            case PacketTypeCode.PingQuery:
            {
                CheckFooter(buffer, ref index);

                if (PingQuery != null)
                {
                    PingQuery.Invoke();
                }

                break;
            }

            case PacketTypeCode.PingResponse:
            {
                CheckFooter(buffer, ref index);

                if (PingResponse != null)
                {
                    PingResponse.Invoke();
                }

                break;
            }

            case PacketTypeCode.SetRssiFilter:
            {
                int value = DecodeRSSIFilterControlPacket(buffer, ref index);
                CheckFooter(buffer, ref index);

                if (RSSIFilterValueChange != null)
                {
                    RSSIFilterValueChange.Invoke(value);
                }

                break;
            }

            case PacketTypeCode.InitiateDeviceSearch:
            {
                CheckFooter(buffer, ref index);

                if (InitiateDeviceSearch != null)
                {
                    InitiateDeviceSearch.Invoke();
                }

                break;
            }

            case PacketTypeCode.StopDeviceSearch:
            {
                CheckFooter(buffer, ref index);

                if (StopDeviceSearch != null)
                {
                    StopDeviceSearch.Invoke();
                }

                break;
            }

            case PacketTypeCode.ConnectToDevice:
            {
                string uid = DecodeDeviceConnectPacket(buffer, ref index);
                CheckFooter(buffer, ref index);

                if (ConnectToDevice != null)
                {
                    ConnectToDevice.Invoke(uid);
                }

                break;
            }

            case PacketTypeCode.DisconnectFromDevice:
            {
                CheckFooter(buffer, ref index);

                if (DisconnectFromDevice != null)
                {
                    DisconnectFromDevice.Invoke();
                }

                break;
            }

            case PacketTypeCode.QueryConnectionStatus:
            {
                CheckFooter(buffer, ref index);

                if (QueryConnectionStatus != null)
                {
                    QueryConnectionStatus.Invoke();
                }

                break;
            }

            case PacketTypeCode.QueryConfig:
            {
                CheckFooter(buffer, ref index);

                if (QueryConfigStatus != null)
                {
                    QueryConfigStatus.Invoke();
                }

                break;
            }

            case PacketTypeCode.SetNewConfig:
            {
                // N.B. This generates a tiny bit of garbage, but avoids race conditions by allocating for every packet
                WearableDeviceConfig config = DeserializeDeviceConfig(buffer, ref index);
                CheckFooter(buffer, ref index);

                if (SetNewConfig != null)
                {
                    SetNewConfig.Invoke(config);
                }

                break;
            }

            case PacketTypeCode.ConfigStatus:
            case PacketTypeCode.SensorFrame:
            case PacketTypeCode.DeviceList:
            case PacketTypeCode.ConnectionStatus:
                // Known, but contextually-invalid packet
                throw new WearableProxyProtocolException(WearableConstants.ProxyProviderInvalidPacketError);

            default:
                // Unknown or corrupt packet
                throw new WearableProxyProtocolException(WearableConstants.ProxyProviderInvalidPacketError);
            }
        }