Skip to content

Commit

Permalink
feature/core-bluetooth (#52)
Browse files Browse the repository at this point in the history
* basic layout for CoreBluetooth transport

* Can discover and connect to devices with service ID AAAA

* add .idea to gitignore

* Can see characteristics of devices

* Can read values of characteristics

* remove .idea

* Move Transport to extension

* Create Extensions folder

* UNTESTED: can probably send and receive abc

* got sending and recieving working

* asthetics

* autocorrect

* some linting

* kinda works

* started cleaning up

* oops

* format

* cleanup

* delete

* refactor

* fixed

* refactor

* removed eol

* major cleanup

* format

* fixed lint issues

* fixed

* disable rule

* done

* minor

* removed comments

* minor

* nodoc & removed unused code

* docs

* cleanups

* removed relayer/main

* receives data, node decodes no longer in transport

* updated

* typer

* bidirectional read / write

* fixes

* updates

* minor update

* notifications

* cleanup

* format

* reformatted

* docs

* Update CoreBluetoothTransport.swift

* Update CoreBluetoothTransport.swift

* Update CoreBluetoothTransport.swift

* Update CoreBluetoothTransport.swift

* docs

* change uuid
  • Loading branch information
tueric authored Sep 12, 2019
1 parent bc3044e commit 5f5efdd
Show file tree
Hide file tree
Showing 47 changed files with 1,955 additions and 94 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ xcuserdata/
*.moved-aside
*.xccheckout
*.xcscmblueprint
.idea
*.xcworkspace

## Obj-C/Swift specific
Expand Down
2 changes: 2 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ identifier_name:
excluded:
- id
- to
disabled_rules:
- trailing_comma
16 changes: 16 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"object": {
"pins": [
{
"package": "SwiftProtobuf",
"repositoryURL": "https://github.com/apple/swift-protobuf.git",
"state": {
"branch": null,
"revision": "3a3594f84b746793c84c2ab2f1e855aaa9d3a593",
"version": "1.6.0"
}
}
]
},
"version": 1
}
10 changes: 10 additions & 0 deletions Sources/UB/Extensions/UUID+Bytes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Foundation

/// :nodoc:
extension UUID {
var bytes: Data {
return withUnsafePointer(to: self) {
Data(bytes: $0, count: MemoryLayout.size(ofValue: self))
}
}
}
17 changes: 17 additions & 0 deletions Sources/UB/Message.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,23 @@ public struct Message: Equatable {
/// The raw message data.
public let message: Data

/// Initializes a message with the passed data.
///
/// - Parameters:
/// - proto: The message protocol.
/// - recipient: The recipient of the message.
/// - from: The previous sender of the message.
/// - origin: The origin of the message, or the original sender.
/// Differs from the `sender` as that changes on every hop.
/// - message: The raw message data.
public init(proto: UBID, recipient: Addr, from: Addr, origin: Addr, message: Data) {
self.proto = proto
self.recipient = recipient
self.from = from
self.origin = origin
self.message = message
}

/// Initializes a Message from a protocol buffer `msg` and a passed `from`.
///
/// - Parameters
Expand Down
30 changes: 23 additions & 7 deletions Sources/UB/Node.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import SwiftProtobuf

// @todo figure out architecture to support new forwarding algorithm.

Expand Down Expand Up @@ -49,41 +50,45 @@ public class Node {
return
}

guard let data = try? message.toProto().serializedData() else {
return
}

transports.forEach { _, transport in
let peers = transport.peers

// @todo ensure that messages are delivered?
// what this does is try to send a message to an exact target or broadcast it to all peers
if message.recipient.count != 0 {
if peers.contains(where: { $0.id == message.recipient }) {
return transport.send(message: message, to: message.recipient)
return transport.send(message: data, to: message.recipient)
}
}

// what this does is send a message to anyone that implements a specific service /
if message.proto.count != 0 {
let filtered = peers.filter { $0.services.contains { $0 == message.proto } }
if filtered.count > 0 {
let sends = send(message, transport: transport, peers: filtered)
let sends = send(message, data: data, transport: transport, peers: filtered)
if sends > 0 {
return
}
}
}

_ = send(message, transport: transport, peers: peers)
_ = send(message, data: data, transport: transport, peers: peers)
}
}

private func send(_ message: Message, transport: Transport, peers: [Peer]) -> Int {
private func send(_ message: Message, data: Data, transport: Transport, peers: [Peer]) -> Int {
var sends = 0
peers.forEach {
if $0.id == message.from || $0.id == message.origin {
return
}

sends += 1
transport.send(message: message, to: $0.id)
transport.send(message: data, to: $0.id)
}

return sends
Expand All @@ -94,7 +99,18 @@ public class Node {

/// :nodoc:
extension Node: TransportDelegate {
public func transport(_: Transport, didReceiveMessage message: Message) {
delegate?.node(self, didReceiveMessage: message)
public func transport(_: Transport, didReceiveData data: Data, from: Addr) {
// @todo message should probably be created here

// @todo delegate should return something where we handle retransmission.

// @todo if node delegate doesn't return anything success, send out the message?

guard let packet = try? Packet(serializedData: data) else {
// @todo
return
}

delegate?.node(self, didReceiveMessage: Message(protobuf: packet, from: from))
}
}
223 changes: 223 additions & 0 deletions Sources/UB/Transports/CoreBluetoothTransport.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import CoreBluetooth
import Foundation

/// CoreBluetoothTransport is used to send and receive message over Bluetooth
public class CoreBluetoothTransport: NSObject, Transport {
/// The transports delegate.
public weak var delegate: TransportDelegate?

/// The peers a specific transport can send messages to.
public fileprivate(set) var peers = [Peer]()

private let centralManager: CBCentralManager
private let peripheralManager: CBPeripheralManager

private static let ubServiceUUID = CBUUID(string: "BEA3B031-76FB-4889-B3C7-000000000000")
private static let receiveCharacteristicUUID = CBUUID(string: "BEA3B031-76FB-4889-B3C7-000000000001")

private static let characteristic = CBMutableCharacteristic(
type: CoreBluetoothTransport.receiveCharacteristicUUID,
properties: [.read, .writeWithoutResponse, .notify],
value: nil,
permissions: [.writeable, .readable]
)

// make this nicer, we need this cause we need a reference to the peripheral?
private var perp: CBPeripheral?
private var centrals = [Addr: CBCentral]()
private var peripherals = [Addr: (peripheral: CBPeripheral, characteristic: CBCharacteristic)]()

/// Initializes a CoreBluetoothTransport with a new CBCentralManager and CBPeripheralManager.
public convenience override init() {
self.init(
centralManager: CBCentralManager(delegate: nil, queue: nil),
peripheralManager: CBPeripheralManager(delegate: nil, queue: nil)
)
}

/// Initializes a CoreBluetoothTransport.
///
/// - Parameters:
/// - centralManager: The CoreBluetooth Central Manager to use.
/// - peripheralManager: The CoreBluetooth Peripheral Manager to use.
public init(centralManager: CBCentralManager, peripheralManager: CBPeripheralManager) {
self.centralManager = centralManager
self.peripheralManager = peripheralManager
super.init()
self.centralManager.delegate = self
self.peripheralManager.delegate = self
}

/// Send implements a function to send messages between nodes using Bluetooth
///
/// - Parameters:
/// - message: The message to send.
/// - to: The recipient address of the message.
public func send(message: Data, to: Addr) {
if let peer = peripherals[to] {
return peer.peripheral.writeValue(
message,
for: peer.characteristic,
type: CBCharacteristicWriteType.withoutResponse
)
}

if let central = centrals[to] {
peripheralManager.updateValue(
message,
for: CoreBluetoothTransport.characteristic,
onSubscribedCentrals: [central]
)
}
}

/// Listen implements a function to receive messages being sent to a node.
public func listen() {
// @todo mark as listening, only turn on peripheral characteristic at this point, etc.
}

fileprivate func remove(peer: Addr) {
peripherals.removeValue(forKey: peer)
peers.removeAll(where: { $0.id == peer })
}

fileprivate func add(central: CBCentral) {
let id = Addr(central.identifier.bytes)

if centrals[id] != nil {
return
}

centrals[id] = central
peers.append(Peer(id: id, services: [UBID]()))
}
}

/// :nodoc:
extension CoreBluetoothTransport: CBPeripheralManagerDelegate {
public func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if peripheral.state == .poweredOn {
let service = CBMutableService(type: CoreBluetoothTransport.ubServiceUUID, primary: true)

service.characteristics = [CoreBluetoothTransport.characteristic]
peripheral.add(service)

peripheral.startAdvertising([
CBAdvertisementDataServiceUUIDsKey: [CoreBluetoothTransport.ubServiceUUID],
CBAdvertisementDataLocalNameKey: nil,
])
}
}

public func peripheralManager(_: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
for request in requests {
guard let data = request.value else {
// @todo
return
}

delegate?.transport(self, didReceiveData: data, from: Addr(request.central.identifier.bytes))
add(central: request.central)
}
}

public func peripheralManager(
_: CBPeripheralManager,
central: CBCentral,
didSubscribeTo _: CBCharacteristic
) {
add(central: central)
}

public func peripheralManager(
_: CBPeripheralManager,
central: CBCentral,
didUnsubscribeFrom _: CBCharacteristic
) {
// @todo check that this is the characteristic
let id = Addr(central.identifier.bytes)
centrals.removeValue(forKey: id)
peers.removeAll(where: { $0.id == id })
}
}

/// :nodoc:
extension CoreBluetoothTransport: CBCentralManagerDelegate {
public func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
centralManager.scanForPeripherals(withServices: [CoreBluetoothTransport.ubServiceUUID])
}

// @todo handling for other states
}

public func centralManager(
_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData _: [String: Any],
rssi _: NSNumber
) {
perp = peripheral
peripheral.delegate = self
centralManager.connect(peripheral)
}

public func centralManager(_: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.discoverServices([CoreBluetoothTransport.ubServiceUUID])
}

public func centralManager(_: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error _: Error?) {
remove(peer: Addr(peripheral.identifier.bytes))
}
}

/// :nodoc:
extension CoreBluetoothTransport: CBPeripheralDelegate {
public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices _: Error?) {
if let service = peripheral.services?.first(where: { $0.uuid == CoreBluetoothTransport.ubServiceUUID }) {
peripheral.discoverCharacteristics([CoreBluetoothTransport.receiveCharacteristicUUID], for: service)
}
}

public func peripheral(
_ peripheral: CBPeripheral,
didDiscoverCharacteristicsFor service: CBService,
error _: Error?
) {
let id = Addr(peripheral.identifier.bytes)
if peripherals[id] != nil {
return
}

let characteristics = service.characteristics
if let char = characteristics?.first(where: { $0.uuid == CoreBluetoothTransport.receiveCharacteristicUUID }) {
peripherals[id] = (peripheral, char)
peripherals[id]?.peripheral.setNotifyValue(true, for: char)
// @todo we may need to do some handshake to obtain services from a peer.
peers.append(Peer(id: id, services: [UBID]()))
}
}

public func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {
if invalidatedServices.contains(where: { $0.uuid == CoreBluetoothTransport.ubServiceUUID }) {
remove(peer: Addr(peripheral.identifier.bytes))
}
}

public func peripheral(
_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic,
error _: Error?
) {
guard let value = characteristic.value else { return }
delegate?.transport(self, didReceiveData: value, from: Addr(peripheral.identifier.bytes))
}

public func peripheral(
_: CBPeripheral,
didUpdateNotificationStateFor _: CBCharacteristic,
error _: Error?
) {
// @todo figure out exactly what we will want to do here.
}
}
2 changes: 1 addition & 1 deletion Sources/UB/Transports/Transport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public protocol Transport {
/// - Parameters:
/// - message: The message to send.
/// - to: The node to which to send the message.
func send(message: Message, to: Addr)
func send(message: Data, to: Addr)

/// Listen implements a function to receive messages being sent to a node.
func listen()
Expand Down
11 changes: 6 additions & 5 deletions Sources/UB/Transports/TransportDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import Foundation

/// TransportDelegate is used to handle the receiving of messages.
/// TransportDelegate is used to handle the receiving data.
public protocol TransportDelegate: AnyObject {
/// Called when a transport receives a new message.
/// Called when a transport receives new data.
///
/// - Parameters:
/// - transport: The transport that received a message.
/// - message: The received message.
func transport(_ transport: Transport, didReceiveMessage message: Message)
/// - transport: The transport that received a data.
/// - data: The received data.
/// - from: The peer from which the data was received.
func transport(_ transport: Transport, didReceiveData data: Data, from: Addr)
}
Loading

0 comments on commit 5f5efdd

Please sign in to comment.