Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/core-bluetooth #52

Merged
merged 63 commits into from
Sep 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
164f555
basic layout for CoreBluetooth transport
ec2 Aug 31, 2019
75b6157
Can discover and connect to devices with service ID AAAA
ec2 Aug 31, 2019
f4d0015
add .idea to gitignore
ec2 Aug 31, 2019
11f0cc3
Can see characteristics of devices
ec2 Aug 31, 2019
396a237
Can read values of characteristics
ec2 Aug 31, 2019
1a39b5f
remove .idea
ec2 Sep 1, 2019
02a1edc
Move Transport to extension
ec2 Sep 1, 2019
4d85b24
Create Extensions folder
ec2 Sep 1, 2019
cddafe4
UNTESTED: can probably send and receive abc
ec2 Sep 1, 2019
183790e
Merge branch 'master' into feature/core-bluetooth
ec2 Sep 2, 2019
d9a6f0b
got sending and recieving working
ec2 Sep 2, 2019
c2af52b
asthetics
ec2 Sep 2, 2019
109ef66
Merge branch 'master' into feature/core-bluetooth
ec2 Sep 2, 2019
4360544
autocorrect
ec2 Sep 5, 2019
0217368
merge conflicts
ec2 Sep 5, 2019
7877229
some linting
ec2 Sep 5, 2019
3eed0ab
Merge branch 'master' into feature/core-bluetooth
ec2 Sep 5, 2019
9f58511
kinda works
ec2 Sep 5, 2019
7aa2eab
started cleaning up
decanus Sep 6, 2019
0006cb1
oops
decanus Sep 6, 2019
a4a9f73
format
decanus Sep 6, 2019
29a441b
cleanup
decanus Sep 6, 2019
e7b25ab
delete
decanus Sep 6, 2019
f368dc3
refactor
decanus Sep 6, 2019
0b171d8
fixed
decanus Sep 6, 2019
ca90346
refactor
decanus Sep 6, 2019
93d66bd
removed eol
decanus Sep 6, 2019
0c9c030
major cleanup
decanus Sep 6, 2019
c70f416
format
decanus Sep 6, 2019
d4675af
fixed lint issues
decanus Sep 6, 2019
cfa4055
fixed
decanus Sep 6, 2019
7790850
disable rule
decanus Sep 6, 2019
81a5973
done
decanus Sep 6, 2019
8f659a8
minor
decanus Sep 6, 2019
34ecca7
removed comments
decanus Sep 6, 2019
a325ff0
minor
decanus Sep 6, 2019
40ebc3b
nodoc & removed unused code
decanus Sep 6, 2019
685ae19
docs
decanus Sep 6, 2019
0cafc29
merged
decanus Sep 7, 2019
c5781b2
cleanups
decanus Sep 7, 2019
ea5f46b
updates
decanus Sep 7, 2019
8a60067
removed relayer/main
decanus Sep 7, 2019
1668d20
receives data, node decodes no longer in transport
decanus Sep 7, 2019
ebd9926
updated
decanus Sep 7, 2019
d5e775e
typer
decanus Sep 9, 2019
66040cd
bidirectional read / write
decanus Sep 9, 2019
0387386
fixes
decanus Sep 9, 2019
925f81b
updates
decanus Sep 9, 2019
5f57ed3
minor update
decanus Sep 9, 2019
d7f554b
notifications
decanus Sep 9, 2019
6dae52e
cleanup
decanus Sep 9, 2019
01e326c
format
decanus Sep 9, 2019
0fb25c9
reformatted
decanus Sep 9, 2019
f981784
docs
decanus Sep 9, 2019
733ad24
Update CoreBluetoothTransport.swift
decanus Sep 9, 2019
ab11691
Update CoreBluetoothTransport.swift
decanus Sep 9, 2019
065599f
Update CoreBluetoothTransport.swift
decanus Sep 9, 2019
1050da7
Update CoreBluetoothTransport.swift
decanus Sep 9, 2019
dbdf612
merged
decanus Sep 9, 2019
48a10fb
Merge branch 'feature/core-bluetooth' of github.com:ultralight-beam/U…
decanus Sep 9, 2019
8000442
merged
decanus Sep 10, 2019
e55286e
docs
decanus Sep 10, 2019
6070337
change uuid
decanus Sep 11, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

message type?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:)

}
}

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