UUID not allowed in peripherial didDiscoverCharacteristicsfor service - swift

I am trying to make two programs that run on separate devices communicate with each other over bluetooth with CoreBluetooth. I can find and connect peripherals from the manager, and I can browse services in connected peripherals, but when I try and try and discover characteristics, I get the error The specified UUID is not allowed for this operation. and as expected the service's characteristics come up nil.
What is this supposed to mean? I have tried to discover characteristics with specifying the UUID of the target and without, both show this error.
this is the function that prints the error.
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
print(error.localizedDescription)//prints "The specified UUID is not allowed for this operation."
if service.characteristics != nil {
for characteristic in service.characteristics! {
if characteristic.uuid == CBUUID(string: "A4389A32-90D2-402F-A3DF-47996E123DC1") {
print("characteristic found")
peripheral.readValue(for: characteristic)
}
}
}
}
this is where I look for peripherals.
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if peripheral.services != nil {
for service in peripheral.services! {
if service.uuid == CBUUID(string: "dc495108-adce-4915-942d-bfc19cea923f") {
peripheral.discoverCharacteristics(nil, for: service)
}
}
}
}
this is how I add the service characteristic on the other device.
service = CBMutableService(type: CBUUID(string:"dc495108-adce-4915-942d-bfc19cea923f"), primary: true)
characteristic = CBMutableCharacteristic(type: CBUUID(string: "A4389A32-90D2-402F-A3DF-47996E123DC1"), properties: .write, value: nil, permissions: .writeable)
service.characteristics = [characteristic]
I tried a number of different combinations of properties and permissions (including .read/.readable) and I get the same error.

You are attempting to read the value of a characteristic that you have set as write-only, so Core Bluetooth gives you an error; the read operation is not valid for the specified characteristic.
If you want your characteristic to be both readable and writable you need to specify this:
service = CBMutableService(type: CBUUID(string:"dc495108-adce-4915-942d-bfc19cea923f"), primary: true)
let characteristic = CBMutableCharacteristic(type: CBUUID(string: "A4389A32-90D2-402F-A3DF-47996E123DC1"), properties: [.write, .read], value: nil, permissions: [.writeable, .readable])
service.characteristics = [characteristic]

Related

Swift Corebluetooth can't get unlimited responses to CBPeripheral .writeValue

I'm trying to get a peripheral to send a response each time my central does the .writeValue command. But it will only send it once.
didConnect
didDiscoverServices
didDiscoverCharacteristicsFor ...
// Enable notify from control characteristic:
for characteristic in serviceCharacteristics where characteristic.uuid == My_sensor_service.sensor_control_characteristicUUID
{
// ...
sensor_control_CBPeripheral = peripheral
sensor_control_CBCharacteristic = characteristic
sensor_control_CBPeripheral!.setNotifyValue(true, for: sensor_control_CBCharacteristic! )
//...
}
A few seconds later...
didUpdateValueFor My_sensor_service.sensor_notification_characteristicUUID ...
// Tell peripheral to send data:
sensor_control_CBPeripheral!.writeValue( command_data, for: sensor_control_CBCharacteristic!, type: .withResponse )
A few seconds later...
didUpdateValueFor My_sensor_service.sensor_control_characteristicUUID
let data = characteristic.value! // got correct data
A few seconds later...
didUpdateValueFor My_sensor_service.sensor_notification_characteristicUUID ...
// Tell peripheral to send more data:
sensor_control_CBPeripheral!.writeValue( command_data, for: sensor_control_CBCharacteristic!, type: .withResponse )
...but, I never get another:
didUpdateValueFor My_sensor_service.sensor_control_characteristicUUID
Peripheral just keeps sending...
didUpdateValueFor My_sensor_service.sensor_notification_characteristicUUID ...

CoreBluetooth and Omron Evolv Blood Pressure Monitor

I have been working on trying to support the Omron Evolv Blood Pressure Monitor (BPM) in my app, via CoreBluetooth. Using the Bluetooth SIG documentation about BPM’s (https://www.bluetooth.com/specifications/specs/ and then BLP and BLS) I could connect with the monitor.
I used the following characteristics:
Blood pressure measurement, 2A35
Blood pressure feature, 2A49
Page 10 in the BLS documentation states that the Blood Pressure Measurement is the property Indicate, which to my knowledge behaves similar to the Notify property.
To clarify some code I call in the delegate methods of CBPeripheralDelegate:
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("connected!")
bloodPressurePeripheral.discoverServices([bloodPressureService])
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard let services = peripheral.services else { return }
services.forEach { service in
print("discovered service: \(service) \n")
peripheral.discoverCharacteristics(nil, for: service)
}
}
In the didDiscoverCharacteristicsFor function I simply loop through the characteristics and check their property.
if char.properties.contains(.read) {
print("\(char.uuid): properties contains .read")
peripheral.readValue(for: char)
}
if char.properties.contains(.indicate) {
print("\(char.uuid): properties contains .indicate")
peripheral.setNotifyValue(true, for: char)
}
if char.properties.contains(.notify) {
print("\(char.uuid): properties contains .notify")
peripheral.setNotifyValue(false, for: char)
I tried both readValue and setNotifyValue for indicate both I still get the following result:
<CBCharacteristic: 0x2829880c0, UUID = 2A35, properties = 0x20, value = (null), notifying = NO>
2A35: properties contains .indicate
<CBCharacteristic: 0x282988180, UUID = 2A49, properties = 0x2, value = {length = 2, bytes = 0x2700}, notifying = NO>
2A49: properties contains .read
I don't really understand why the value of 2A35 is null. I know there are values because with the Omron application I can get the measurements.
My actual questions is: Has anyone has any experience in connecting with (Omron) BPM's using CoreBluetooth and what am I overlooking?
Thanks for answering!
I have tried connecting Ormon Evolv with Android device.
I will tell you what I have learned from it.
NB:- Ble Devices communicates asynchronously, you have to do one GATT operation (eg, read, write, enable notification, enable indication) at a time. The next operation is to be done only after the previous one is successfully done.
My device had the following services.
DEVICE_INFO_SERVICE with UUID 180a
BATTERY_SERVICE_UUID with UUID 180f
CURRENT_TIME_SERVICE_UUID with UUID 1805
BLOOD_PRESSURE_SERVICE_UUID with UUID 1810
The first GATT operation after a successful connect is gatt.discoverServices() . (the event onServicesDiscoverd is trigged, in android)
If you just need the BP reading without reading status and time stamp, just enable indication for UUID 2A35
val bloodPressureService = gatt?.getService(BLOOD_PRESSURE_SERVICE_UUID)
val bloodPressureChar = bloodPressureService?.getCharacteristic(BLOOD_PRESSURE_CHAR_UUID)
gatt?.setCharacteristicNotification(bloodPressureChar, true)
val bloodDescriptor = bloodPressureChar?.getDescriptor(CCC_DESCRIPTOR_UUID)
bloodDescriptor?.value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
gatt?.writeDescriptor(bloodDescriptor)

Allow only certain apps to use my VPN - Swift

I am making an app that activates a VPN connection based on OpenVPN, retrieves a certificate from the database, and opens a tunnel using NEPacketTunnelProvider and NetworkExtension.
I used the following repository, and now my VPN is working fine.
But the problem is that I want to allow only one app to use this VPN when enabled (WhatsApp precisely), and I want to restrict all other apps of using it.
On Android it's possible by giving the bundle identifier of the allowed apps to the PackageManager.
Can you please help me?
This is my PacketTunnelProvider class:
import NetworkExtension
import OpenVPNAdapter
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
class PacketTunnelProvider: NEPacketTunnelProvider {
lazy var vpnAdapter: OpenVPNAdapter = {
let adapter = OpenVPNAdapter()
adapter.delegate = self
return adapter
}()
let vpnReachability = OpenVPNReachability()
var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?
override func startTunnel(options: [String : NSObject]?, completionHandler: #escaping (Error?) -> Void) {
// There are many ways to provide OpenVPN settings to the tunnel provider. For instance,
// you can use `options` argument of `startTunnel(options:completionHandler:)` method or get
// settings from `protocolConfiguration.providerConfiguration` property of `NEPacketTunnelProvider`
// class. Also you may provide just content of a ovpn file or use key:value pairs
// that may be provided exclusively or in addition to file content.
// In our case we need providerConfiguration dictionary to retrieve content
// of the OpenVPN configuration file. Other options related to the tunnel
// provider also can be stored there.
print("started!")
guard
let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration
else {
fatalError()
}
guard let ovpnFileContent: Data = providerConfiguration["ovpn"] as? Data else {
fatalError()
}
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnFileContent
// configuration.settings = [
// // Additional parameters as key:value pairs may be provided here
// ]
// Uncomment this line if you want to keep TUN interface active during pauses or reconnections
// configuration.tunPersist = true
// Apply OpenVPN configuration
let evaluation: OpenVPNConfigurationEvaluation
do {
evaluation = try vpnAdapter.apply(configuration: configuration)
} catch {
completionHandler(error)
return
}
// Provide credentials if needed
if !evaluation.autologin {
// If your VPN configuration requires user credentials you can provide them by
// `protocolConfiguration.username` and `protocolConfiguration.passwordReference`
// properties. It is recommended to use persistent keychain reference to a keychain
// item containing the password.
guard let username: String = protocolConfiguration.username else {
fatalError()
}
// Retrieve a password from the keychain
// guard let password: String = ... {
// fatalError()
// }
let credentials = OpenVPNCredentials()
credentials.username = username
// credentials.password = password
do {
try vpnAdapter.provide(credentials: credentials)
} catch {
completionHandler(error)
return
}
}
// Checking reachability. In some cases after switching from cellular to
// WiFi the adapter still uses cellular data. Changing reachability forces
// reconnection so the adapter will use actual connection.
vpnReachability.startTracking { [weak self] status in
guard status == .reachableViaWiFi else { return }
self?.vpnAdapter.reconnect(afterTimeInterval: 5)
}
// Establish connection and wait for .connected event
startHandler = completionHandler
vpnAdapter.connect(using: packetFlow)
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: #escaping () -> Void) {
stopHandler = completionHandler
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
vpnAdapter.disconnect()
}
}
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
// `completionHandler` callback requires an object conforming to `OpenVPNAdapterPacketFlow`
// protocol if the tunnel is configured without errors. Otherwise send nil.
// `OpenVPNAdapterPacketFlow` method signatures are similar to `NEPacketTunnelFlow` so
// you can just extend that class to adopt `OpenVPNAdapterPacketFlow` protocol and
// send `self.packetFlow` to `completionHandler` callback.
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?, completionHandler: #escaping (Error?) -> Void) {
// In order to direct all DNS queries first to the VPN DNS servers before the primary DNS servers
// send empty string to NEDNSSettings.matchDomains
networkSettings?.dnsSettings?.matchDomains = [""]
// Set the network settings for the current tunneling session.
setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
}
// Process events returned by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleEvent event: OpenVPNAdapterEvent, message: String?) {
switch event {
case .connected:
if reasserting {
reasserting = false
}
guard let startHandler = startHandler else { return }
startHandler(nil)
self.startHandler = nil
case .disconnected:
guard let stopHandler = stopHandler else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
stopHandler()
self.stopHandler = nil
case .reconnecting:
reasserting = true
default:
break
}
}
// Handle errors thrown by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
// Handle only fatal errors
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool, fatal == true else {
return
}
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
if let startHandler = startHandler {
startHandler(error)
self.startHandler = nil
} else {
cancelTunnelWithError(error)
}
}
// Use this method to process any log message returned by OpenVPN library.
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
// Handle log messages
print(logMessage)
}
}
This is the function used in my VPN View Model to start a tunnel:
func configureVPN(serverAddress: String, username: String, password: String) {
var configData:Data = Data.init()
self.getCertificate{certificate in
configData = certificate!
guard
//If we want to read from a file
// let configData = self.readFile(name: "vtest2"),
let providerManager = self.providerManager
else {
return
}
self.providerManager?.loadFromPreferences { error in
if error == nil {
let tunnelProtocol = NETunnelProviderProtocol()
tunnelProtocol.username = username
tunnelProtocol.serverAddress = serverAddress
tunnelProtocol.providerBundleIdentifier = self.providerId // bundle id of the network extension target
tunnelProtocol.providerConfiguration = ["ovpn": configData]
tunnelProtocol.disconnectOnSleep = false
providerManager.protocolConfiguration = tunnelProtocol
providerManager.localizedDescription = "Slyfone Guard" // the title of the VPN profile which will appear on Settings
providerManager.isEnabled = true
providerManager.saveToPreferences(completionHandler: { (error) in
if error == nil {
providerManager.loadFromPreferences(completionHandler: { (error) in
do {
try providerManager.connection.startVPNTunnel(options: nil) // starts the VPN tunnel.
} catch let error {
print(error.localizedDescription)
}
})
}
})
}
}
}
}
As an engineer from Apple said:
The way to do it is to use Per-App VPN. See the Per-App VPN On Demand section in the NETunnelProviderManager documentation.
With NEPacketTunnelProvider on macOS (as of 10.15.4) you can set this up yourself with NEAppRule. A very generic example of setting up Safari to trigger the VPN would be:
var perAppManager = NETunnelProviderManager.forPerAppVPN()
/* ... */
NETunnelProviderManager.forPerAppVPN().loadFromPreferences(completionHandler: { error in
precondition(Thread.isMainThread)
/* ... */
let proto = (perAppManager.protocolConfiguration as? NETunnelProviderProtocol) ?? NETunnelProviderProtocol()
proto.serverAddress = "server.vpn.com"
proto.providerBundleIdentifier = "com.perapp-vpn.macOSPacketTunnel.PacketTunnelTest"
var appRules = [NEAppRule]()
let appRule = NEAppRule(signingIdentifier: "com.apple.Safari", designatedRequirement: "identifier \"com.apple.Safari\" and anchor apple")
appRule.matchDomains = ["example.com"]
appRules.append(appRule)
perAppManager.appRules = appRules
perAppManager.isOnDemandEnabled = true
perAppManager.protocolConfiguration = proto
perAppManager.isEnabled = true
perAppManager.localizedDescription = "Testing Per-App VPN"
self.perAppManager.saveToPreferences { saveError in
/* Proceed to connect */
}
})
That was a very generic case and forPerAppVPN() is only available on macOS. A more real-world case world case for iOS would be to create this process through MDM. That entire flow is explained in the documentation I mentioned previously. I would start by just creating a configuration profile in Configurator 2 and testing it out.
No idea if it works on OpenVPN

Can't write JSON to peripheral with CoreBluetooth

I have a paired bluetooth peripheral which I have to send some credentials in a JSON like as follows
{"SSID":"WIFI_SSID", "Password": "WIFI_PASSWORD"}
And after the information has been sent the peripheral should connect to the provided WiFi credentials but I'm not completely sure the process is being performed correctly.
As soon the bluetooth peripheral has connected I start the process,
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
// As soon as the device has connected
peripheral.delegate = self
// Only discover the service I know it's for writing
peripheral.discoverServices([SERVICE_UUID])
}
The discoverServices will call the peripheral(:didDiscoverServices error)
func peripheral( _ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
for service in peripheral.services! {
peripheral.discoverCharacteristics(nil, for: service)
}
}
Calling the following method where I do all the logic
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
for characteristic in service.characteristics! {
let characteristic = characteristic as CBCharacteristic
debugPrint(characteristic.properties)
// This prints the following properties, reading + writing
// __C.CBCharacteristicProperties(rawValue: 10)
if characteristic.uuid.isEqual(RX_UUID) {
do {
let msgDictionary = ["SSID": wiFiCredentials.SSID, "Password": wiFiCredentials.Password]
let jsonData = try JSONSerialization.data(withJSONObject: msgDictionary,options:[])
peripheral.writeValue(jsonData, for: characteristic, type: CBCharacteristicWriteType.withResponse)
} catch let error as NSError {
debugPrint("Error in auth message JSON: \(error.description)")
}
}
}
}
Up to this point I think everything it's correct. After calling writeValue and setting the type to CBCharacteristicWriteType.withResponse I should expect something in the peripheral(:didWriteValueFor characteristic: error) method. What I receive in that method is the next error
Error Domain=CBATTErrorDomain Code=3 \"Writing is not permitted.\" UserInfo={NSLocalizedDescription=Writing is not permitted.}"
What I guess is that when I write the JSON value I shouldn't use the .withResponse flag and use .withoutResponse. If I do so I get the following log in the console.
[CoreBluetooth] WARNING: Characteristic <CBCharacteristic: 0x28388a040, UUID = 3E9D2532-2F00-11E9-9602-A44CC81C989A, properties = 0xA, value = (null), notifying = NO> does not specify the "Write Without Response" property - ignoring response-less write
Which confirms to me that I have to use the .writeWithResponse.
Is there something I am missing in the process? The JSON has to be sent using GATT and AFAIK this is the correct approach to do it. Are the printed CBCharacteristicProperties correct?
Things I've done:
The JSON is not the problem. I've tried writing a random variable "1".data(using: .ascii) and still receive the same error.

Swift BLE peripheral writeValue is not working

My current code is:
#IBAction func sendData(sender: UISwitch) {
if advertisingSwitch.on {
var parameter = NSInteger(45)
let data = NSData(bytes: &parameter, length: 1)
if let connectedPeripheral = discoveredPeripheral {
println("========= In connected peripheral \(connectedPeripheral)")
//println("========= Send data is \(currentSendData)")
println("========= Characteristic is \(sendDataCharacteristic)")
println("========= data length is \(data.bytes)")
self.sendDataToCentral(connectedPeripheral, characteristic: sendDataCharacteristic!, data: data)
}
}
}
private func sendDataToCentral(peripheral: CBPeripheral, characteristic: CBCharacteristic, data: NSData) {
println("data is \(data)")
peripheral.writeValue(data, forCharacteristic: characteristic, type: CBCharacteristicWriteType.WithoutResponse)
println("writed characteristic \(characteristic)")
}
When I checked the Peripheral, it is connected showing:
BPeripheral: 0x1700ee980, identifier = E2377588-84CB-87ED-570A-B51614287B3C, name = TAv22u-FDF1, state = connected
The characteristic is getting from service scan with known UUID. I am sure the characteristic I got has function "write", which is
<CBCharacteristic: 0x174086090, UUID = FFE9, properties = 0x8, value = (null), notifying = NO>
after I executing function:
peripheral.writeValue(data, forCharacteristic: characteristic, type: CBCharacteristicWriteType.WithoutResponse)
The value in characteristic is not changing. I don't know where did I get wrong.
peripheral.writeValue(data, forCharacteristic: characteristic, type:
CBCharacteristicWriteType.WithResponse)
try .withResponse and check what is response from peripheral.