ChatHandler
class ChatHandler : NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralManagerDelegate, CBPeripheralDelegate
The Bluetooth Manager handles all searching for, creating connection to and sending/receiving messages to/from other Bluetooth devices.
This class handles almost all the logic in the app and is passed around to the different views such that they all have access to the same Bluetooth objects as well as conversations. When the app is launched all the information is stored in memory and written to the persistent storage as needed.
Note
It conforms to a variety of delegates which is used for callback functions from the Apple APIs.Note
In code the ChatBrain has been divided into files for seperation and isolation of features.-
Context for CoreData storage
Declaration
Swift
var context: NSManagedObjectContext
-
A simple counter to show amount of relayed messages this session. It is reset when the app is force-closed or the device is restarted.
Declaration
Swift
@Published var routedCounter: Int { get set }
-
A UUID which is updated when a ACK message is retrieved. This forces a refresh of the
ChatView
and the message status is updated.Declaration
Swift
@Published var refreshID: UUID { get set }
-
Holds an array of messages to be delivered at a later point. Used for the queue functionality.
Declaration
Swift
@Published var messageQueue: [queuedMessage] { get set }
-
Holds a reference to all devices discovered. If no reference is held then the Bluetooth connection may be dropped.
Declaration
Swift
@Published var discoveredDevices: [Device] { get set }
-
Holds the connected characteristics. This is only used for the chat functionality for now.
Declaration
Swift
var connectedCharateristics: [CBCharacteristic]
-
The centralManager acts as our Bluetooth server and receives messages sent by clients to the server.
Declaration
Swift
var centralManager: CBCentralManager!
-
The peripheralManager acts as our Bluetooth clients and establishes connections to other BT servers. It also sends messages.
Declaration
Swift
var peripheralManager: CBPeripheralManager!
-
The characteristic which defines our chat functionality for the Bluetooth API.
Declaration
Swift
var characteristic: CBMutableCharacteristic?
-
A list which holds message IDs which we have seen before. This prevents looping them in the network for ages.
Declaration
Swift
var seenMessages: [Int32]
-
A dictionary which stores how many messages we have received from a connected peripheral. It is cleaned from time to time as well.
Declaration
Swift
var peripheralMessages: [String : [Date]]
-
A dictionary which holds the ids of messages relayed and the corresponding sender of said messages. This is used for DSR.
Declaration
Swift
var senderOfMessageID: [Int32 : String]
-
Seen CBCentrals / This is used for DSR algorithm.
Declaration
Swift
var seenCBCentral: [CBCentral]
-
The initialiser for the ChatBrain. Sets up the
centralManager
and theperipheralManager
.Declaration
Swift
init(context: NSManagedObjectContext)
Parameters
context
The context for persistent storage to
CoreData
-
Remove a device from discoveredDevices and drop connection to it.
Declaration
Swift
func cleanUpPeripheral(_ peripheral: CBPeripheral)
Parameters
peripheral
The peripheral to remove and drop connection to.
-
Undocumented
Declaration
Swift
public func handleScan(result: String)
-
Callback function which gets the Bluetooth state of this device.
If Bluetooth is turned on and functions correctly we will start scanning for peripherals.
Note
Cases such as .poweredOff are not handled right now. In the future they should be.Declaration
Swift
func centralManagerDidUpdateState(_ central: CBCentralManager)
Parameters
central
The Central Manager which has its state updated. Given by Apple APIs.
-
Callback function which is activated if a peripheral is discovered.
This means that if this function is called a new device is nearby and ready to broadcast messages for us.
Note
Converts the peripheral to a Device() and stores it in memory. Otherwise the connection would be dropped.Declaration
Swift
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber)
Parameters
central
The central manager which discovers the peripheral.
peripheral
The peripheral which is discovered.
advertisementData
Holds information such as the name of the peripheral. See more in Apples docs.
RSSI
The signal strength to the peripheral device.
-
Callback function if the central manager connected successfully to the peripheral.
Afterwards we discover what services it has to offer, and checks that it supports the dIM identifier. Otherwise it could be all other Bluetooth devices.
Declaration
Swift
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral)
Parameters
central
The central manager which connects to the peripheral.
peripheral
The peripheral which we connected successfully to.
-
Callback function if we fail to connect to some peripheral.
Note
No error handling has been implemented yet.Declaration
Swift
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?)
Parameters
central
The central manager which fails to connect.
peripheral
The peripheral which we fail to connect to.
error
The error description of the failed connection.
-
Callback function called when we lose connection to a peripheral.
This function cleans up the peripheral and removes it from memory.
Note
Should remove the device from theconnectedDevices
array as well (in the future).Declaration
Swift
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?)
Parameters
central
The central which loses its connection to a peripheral.
peripheral
The peripheral device which we lose connection to.
error
The error description of the lost connection. (out-of-range for example)
-
Callback function whenever a peripheral updates its RSSI.
Note
RSSI is the signal strength.Declaration
Swift
func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?)
Parameters
peripheral
The peripheral which updates its RSSI.
RSSI
The new RSSI value for said peripheral.
error
The error description if there are any. Printed to console.
-
Callback function if we discover the dIM UUID on a peripheral device.
Afterwards we look for the specific characteristic which defines our chat functionality.
Declaration
Swift
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?)
Parameters
peripheral
The peripheral in which we discover a service.
error
The error description if there are any. Printed to the console.
-
Callback function if we discover the characteristic which defines the chat functionality.
Note
This means that we are fully connected and ready to receive messages.Declaration
Swift
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?)
Parameters
peripheral
The now fully connected peripheral.
service
The service which we have discovered, only chat functionality is provided.
error
The error description if there are any. Printed to the console.
-
Callback function which does error handling if we receive the wrong notifications.
Notifications are new messages.
Declaration
Swift
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?)
Parameters
peripheral
The peripheral which send the notification.
characteristic
The characteristic which it sends notification for (chat functionality).
error
Error description if any. Printed to the console.
-
Callback function which is called whenever we receive a new message.
Checks that we are not receiving too many messages from this particular device and that we are not getting DOS attacked.
The message is then decoded from JSON and passed to our
receivedMessage
.Declaration
Swift
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)
Parameters
peripheral
The peripheral which we receive a new message from.
characteristic
The chateristic which we receive a new message from.
error
Error description if there are any. Also printed to the console.
-
Callback function if a peripheral modifies its services. This is not allowed and we therefore drop our connection to it.
Declaration
Swift
func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService])
Parameters
peripheral
The peripheral modifying its services.
invalidatedServices
The service which it invalidates.
-
Start advertisting that we are here to nearby Bluetooth central managers. This is a callback function and called automatically.
We also set our devices name here.
Note
The devices name is not avaiable when the app is backgrounded due to API restrictions.Declaration
Swift
func startAdvertising(peripheralManager: CBPeripheralManager)
Parameters
peripheralManager
The peripheral manager to start advertising for.
-
Callback function which is called when the peripheral manager updates its state.
This could be due to Bluetooth being turned off by the user. In the future we may notify the user that the wont be able to receive messages when they turned off Bluetooth.
When the peripheral updates its state to
poweredOn
we add the Bluetooth functionality to it as a characteristic.Declaration
Swift
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager)
Parameters
peripheral
The peripheral which updates its state.
-
Called when a new central subscribes to our peripheral manager. This means that we have someone to send messages to other than ourselves.
Declaration
Swift
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic)
Parameters
peripheral
The peripheral which has a new subscription.
central
The central which subscribes to the peripheral.
characteristic
The characteristic which it subscribes to.
-
Callback function activated when a central unsubscribes from us.
Nothing is done at the minute but error handling should be done in the future.
Declaration
Swift
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic)
Parameters
peripheral
The peripheral which is unsubscribed from.
central
The central which unsubscribed from us.
characteristic
The characteristic (functionality) which the central unsubscribes from.
-
Add a message to the dicitionary when it is received.
This is to ensure that we have knowledge of where a message came from to send back
ACK
messages the shortest route possible.Declaration
Swift
func addMessageToDSRTable(messageID: Int32, bluetoothID: String)
Parameters
messageID
The id of the message to keep track of.
bluetoothID
The id of the sender of the message to save.
-
Check if we have seen a message before. If we have not we will flood the network.
Declaration
Swift
func checkMessageSeenBefore(messageID: Int32) -> Bool
Parameters
messageID
The id of the message to check.
Return Value
A boolean to confirm if we have seen the message before.
-
Get the sender of a message given a message id.
Declaration
Swift
func getSenderOfMessage(messageID: Int32) -> String
Parameters
messageID
The message id to check for.
Return Value
The Bluetooth UUID as a string.
-
A message stored together with a date for queue.
See moreDeclaration
Swift
struct queuedMessage
-
Add a message to the message queue.
This function is used to deliver messages over long distances.
Declaration
Swift
func messageQueueAdd(_ message: Message)
Parameters
message
The message to add to the queue of messages.
-
Remove old messages from the message queue such that we do not deliver messages older than a set amount.
Declaration
Swift
func checkMessageQueue()
-
Called when we establish a new connection to a central manager. This sends over all the queued messages but clean up first such that no old messages are sent.
Declaration
Swift
func messageQueueNewConnection(_ central: CBCentral)
Parameters
central
The newly connected central device.
-
Relay a message which we have received and is not for us.
This function simply sends all received messages not for us to all connected servers (central managers).
Declaration
Swift
func relayMessage(_ message: Message)
Parameters
message
The message to relay still in its encrypted format.
-
Relay message but to be used for
DSR
routing. Therefore we require a specific Bluetooth ID to send the send the message to.Note
This function is only used forACK
messages currently.Declaration
Swift
func relayMessage(_ message: Message, _ bluetoothID: String)
Parameters
message
The message to send.
bluetoothID
The BluetoothID to send it to (a UUID as a string).
-
Retrieve a message from a sender and handle it accordingly.
If the message is not for us we relay it to connected Bluetooth centrals. Otherwise, if it is for us, we start decrypting it and adding it to the correct conversation. Then we send an
ACK
message to confirm that we have received it.Declaration
Swift
func retrieveMessage(_ messageEncrypted: Message)
Parameters
messageEncrypted
The message that we have received. Then we determine if it is for us.
-
Called if we have received a
READ
message type. This is to handle that type of messages correctly.Also confirms that the message is formatted correctly.
Declaration
Swift
func receivedRead(message: Message, conversation: ConversationEntity) -> Bool
Parameters
message
The
READ
message that we have received.conversation
The conversation in which the message is to be handled.
Return Value
A boolean that confirms that the type of message is a
READ
type. -
Handles
ACK
message types. Also confirms that the message is correctly formatted and updates the conversation.Declaration
Swift
func receivedAck(message: Message, conversation: ConversationEntity) -> Bool
Parameters
message
The
ACK
message we have received.conversation
The conversation in which it is handled.
Return Value
A boolean confirming that it is or is not an
ACK
message.
-
A helper function to decrypt a message to a string.
Declaration
Swift
func decryptRetrievedMessageToString(message: Message, conversation: ConversationEntity) -> String?
Parameters
message
The message to decrypt.
conversation
The conversation to decrypt the message for.
Return Value
The decrypted content of the message or nil if it cannot be decrypted.
-
Sends a message to a specific user.
This is done by encrypting the content of the message, generating new id’s and passing it along to the peripheral manager to send out to connected central managers.
Declaration
Swift
func sendMessage(for conversation: ConversationEntity, text message: String, context: NSManagedObjectContext)
Parameters
conversation
The conversation for whom we want to send a message.
message
The message that we want to send. It is encrypted in this function.
context
The context which we save the message. Used for persistent storage to CoreData.
-
Sends a message of id’s which confirms that we have read the received messages.
This function is only called if we have enabled the
read
functionality in the settings menu.We send a message with all the id’s of the messages that we have read formatted as
READ/id1/id2/id2...
.Declaration
Swift
func sendReadMessage(_ conversation: ConversationEntity)
Parameters
conversation
The conversation which we have recently opened.
-
Sends an
ACK
message to confirm that we have received a message. If we use DSR (Dynamic Source Routing) this messages is sent on the shortest route possible.Declaration
Swift
func sendAckMessage(_ message: MessageEntity)
Parameters
message
The message which we want to send an ACK message for.