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

enhancement/bluetooth-l2cap #61

Closed
wants to merge 79 commits into from
Closed
Show file tree
Hide file tree
Changes from 77 commits
Commits
Show all changes
79 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
a2ddd2f
started working on generic stream code
decanus Sep 11, 2019
5c9bf84
removed peer
decanus Sep 11, 2019
c08dce5
updated
decanus Sep 11, 2019
b2f4914
rearranged
decanus Sep 11, 2019
f6f5779
cleanup
decanus Sep 11, 2019
aead1d6
added todo
decanus Sep 11, 2019
b43281c
removed
decanus Sep 12, 2019
39fbb05
more stream work
decanus Sep 17, 2019
522fae4
bunch of experiments
decanus Sep 17, 2019
c153bbb
merge
decanus Sep 17, 2019
1e06653
eol
decanus Sep 17, 2019
06e18c9
formatted
decanus Sep 17, 2019
5f1d700
cleanuo
decanus Sep 17, 2019
08864e1
hard reference
decanus Sep 18, 2019
b28a423
removal
decanus Sep 18, 2019
0c81cca
removed todo, no longer valid as we figured it out for corebluetooth
decanus Sep 18, 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
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PackageDescription
let package = Package(
name: "UB",
platforms: [
.macOS(.v10_13),
.macOS(.v10_14),
],
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
Expand Down
17 changes: 0 additions & 17 deletions Podfile.lock

This file was deleted.

9 changes: 9 additions & 0 deletions Sources/UB/Extensions/Numeric+Data.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Foundation
Copy link

Choose a reason for hiding this comment

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

Similar blocks of code found in 2 locations. Consider refactoring.


extension Numeric {
var bytes: Data {
return withUnsafePointer(to: self) {
Data(bytes: $0, count: MemoryLayout.size(ofValue: self))
}
}
}
2 changes: 1 addition & 1 deletion Sources/UB/Node.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public class Node {
}
}

// what this does is send a message to anyone that implements a specific service /
// 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 {
Expand Down
153 changes: 114 additions & 39 deletions Sources/UB/Transports/CoreBluetoothTransport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ public class CoreBluetoothTransport: NSObject, Transport {
// 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)]()

private var streams = [Addr: StreamClient]()

private var psm: CBL2CAPPSM?

/// Initializes a CoreBluetoothTransport with a new CBCentralManager and CBPeripheralManager.
public convenience override init() {
Expand Down Expand Up @@ -54,21 +57,11 @@ public class CoreBluetoothTransport: NSObject, Transport {
/// - 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
)
guard let stream = streams[to] else {
return
}

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

/// Listen implements a function to receive messages being sent to a node.
Expand All @@ -77,7 +70,7 @@ public class CoreBluetoothTransport: NSObject, Transport {
}

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

Expand All @@ -91,6 +84,12 @@ public class CoreBluetoothTransport: NSObject, Transport {
centrals[id] = central
peers.append(Peer(id: id, services: [UBID]()))
}

fileprivate func add(channel: CBL2CAPChannel) {
let client = L2CAPStreamClient(channel: channel)
client.delegate = self
streams[Addr(channel.peer.identifier.bytes)] = client
}
}

/// :nodoc:
Expand All @@ -102,13 +101,17 @@ extension CoreBluetoothTransport: CBPeripheralManagerDelegate {
service.characteristics = [CoreBluetoothTransport.characteristic]
peripheral.add(service)

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

public func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error _: Error?) {
peripheral.startAdvertising([
CBAdvertisementDataServiceUUIDsKey: [service.uuid],
CBAdvertisementDataLocalNameKey: nil,
])
}

public func peripheralManager(_: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
for request in requests {
guard let data = request.value else {
Expand All @@ -121,24 +124,55 @@ extension CoreBluetoothTransport: CBPeripheralManagerDelegate {
}
}

public func peripheralManager(
_: CBPeripheralManager,
central: CBCentral,
didSubscribeTo _: CBCharacteristic
) {
public func peripheralManager(_: CBPeripheralManager, central: CBCentral, didSubscribeTo _: CBCharacteristic) {
guard let psm = psm else {
return
}

add(central: central)
update(value: psm.bytes)
}

public func peripheralManager(
_: CBPeripheralManager,
central: CBCentral,
didUnsubscribeFrom _: CBCharacteristic
) {
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 })
}

public func peripheralManager(_: CBPeripheralManager, didPublishL2CAPChannel PSM: CBL2CAPPSM, error _: Error?) {
psm = PSM

guard centrals.count > 0 else {
return
}

update(value: PSM.bytes)
}

public func peripheralManager(_: CBPeripheralManager, didUnpublishL2CAPChannel _: CBL2CAPPSM, error _: Error?) {
// @todo
}

public func peripheralManager(_: CBPeripheralManager, didOpen channel: CBL2CAPChannel?, error: Error?) {
Copy link

Choose a reason for hiding this comment

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

Similar blocks of code found in 2 locations. Consider refactoring.

if error != nil {
// @todo handle
}

guard let channel = channel else {
return
}

add(channel: channel)
}

private func update(value: Data) {
peripheralManager.updateValue(
value,
for: CoreBluetoothTransport.characteristic,
onSubscribedCentrals: Array(centrals.values)
)
}
}

/// :nodoc:
Expand Down Expand Up @@ -182,20 +216,29 @@ extension CoreBluetoothTransport: CBPeripheralDelegate {
public func peripheral(
_ peripheral: CBPeripheral,
didDiscoverCharacteristicsFor service: CBService,
error _: Error?
error: Error?
) {
let id = Addr(peripheral.identifier.bytes)
if peripherals[id] != nil {
return
if error != nil {
// @todo
}

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]()))
peripheral.setNotifyValue(true, for: char)
}

// 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]) {
Expand All @@ -207,10 +250,19 @@ extension CoreBluetoothTransport: CBPeripheralDelegate {
public func peripheral(
_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic,
error _: Error?
error: Error?
) {
if error != nil {
// @todo
}

guard let value = characteristic.value else { return }
delegate?.transport(self, didReceiveData: value, from: Addr(peripheral.identifier.bytes))

let psm = value.withUnsafeBytes {
$0.load(as: UInt16.self)
}

peripheral.openL2CAPChannel(psm)
}

public func peripheral(
Expand All @@ -220,4 +272,27 @@ extension CoreBluetoothTransport: CBPeripheralDelegate {
) {
// @todo figure out exactly what we will want to do here.
}

public func peripheral(_: CBPeripheral, didOpen channel: CBL2CAPChannel?, error: Error?) {
Copy link

Choose a reason for hiding this comment

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

Similar blocks of code found in 2 locations. Consider refactoring.

if error != nil {
// @todo handle
}

guard let channel = channel else {
return
}

add(channel: channel)
}
}

/// :nodoc:
extension CoreBluetoothTransport: StreamClientDelegate {
public func client(_ client: StreamClient, didReceiveData data: Data) {
guard let peer = streams.first(where: { $0.value == client })?.key else {
return // @todo log?
}

delegate?.transport(self, didReceiveData: data, from: peer)
}
}
13 changes: 13 additions & 0 deletions Sources/UB/Transports/Stream/L2CAPStreamClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import CoreBluetooth

// This class exists because we need a strong reference to the CBL2CAPChannel

class L2CAPStreamClient: StreamClient {

let channel: CBL2CAPChannel

init(channel: CBL2CAPChannel) {
self.channel = channel
super.init(input: channel.inputStream, output: channel.outputStream)
}
}
78 changes: 78 additions & 0 deletions Sources/UB/Transports/Stream/StreamClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import Foundation

/// The StreamClient implements generic stream handling for Ultralight Beam transports.
public class StreamClient: NSObject {
// @TODO: We need to figure out how the dependants figure out which address or peer data came from.
Copy link

Choose a reason for hiding this comment

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

TODOs should be resolved (We need to figure out how the ...).


/// The delegate for the StreamClient.
weak var delegate: StreamClientDelegate?

private let input: InputStream
private let output: OutputStream

/// Initializes a StreamClient with the input and output streams.
///
/// - Parameters:
/// - input: The input stream.
/// - output: The output stream.
public init(input: InputStream, output: OutputStream) {
Copy link

Choose a reason for hiding this comment

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

Similar blocks of code found in 2 locations. Consider refactoring.

self.input = input
self.output = output

super.init()

self.input.delegate = self
self.output.delegate = self
}

/// Writes the contents of provided data to the receiver.
///
/// - Parameters:
/// - data: Data to write
public func write(_ data: Data) {
guard output.hasSpaceAvailable else {
// @todo error out or some shit?
return
}

var length = UInt32(data.count).bigEndian

let bytes = NSMutableData()
bytes.append(&length, length: 4)
bytes.append(data)

output.write([UInt8](bytes), maxLength: bytes.count)
}
}

/// :nodoc:
extension StreamClient: StreamDelegate {
public func stream(_: Stream, handle eventCode: Stream.Event) {
// @todo handle all other cases

switch eventCode {
case .hasBytesAvailable:
read()
default:
return
}
}

fileprivate func read() {
let length = read(4).withUnsafeBytes {
$0.load(as: UInt32.self)
}

delegate?.client(self, didReceiveData: read(Int(length.bigEndian)))
}

private func read(_ length: Int) -> Data {
if length == 0 {
return Data()
}

var buffer = [UInt8](repeating: 0, count: length)
input.read(&buffer, maxLength: length)
return Data(buffer)
}
}
11 changes: 11 additions & 0 deletions Sources/UB/Transports/Stream/StreamClientDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

/// An interface used to handle events on the StreamClient.
protocol StreamClientDelegate: AnyObject {
/// This method is called when a client receives new data.
///
/// - Parameters:
/// - client: The client which received data.
/// - data: The data that was received.
func client(_ client: StreamClient, didReceiveData data: Data)
}