-
Notifications
You must be signed in to change notification settings - Fork 2
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
Changes from 77 commits
164f555
75b6157
f4d0015
11f0cc3
396a237
1a39b5f
02a1edc
4d85b24
cddafe4
183790e
d9a6f0b
c2af52b
109ef66
4360544
0217368
7877229
3eed0ab
9f58511
7aa2eab
0006cb1
a4a9f73
29a441b
e7b25ab
f368dc3
0b171d8
ca90346
93d66bd
0c9c030
c70f416
d4675af
cfa4055
7790850
81a5973
8f659a8
34ecca7
a325ff0
40ebc3b
685ae19
0cafc29
c5781b2
ea5f46b
8a60067
1668d20
ebd9926
d5e775e
66040cd
0387386
925f81b
5f57ed3
d7f554b
6dae52e
01e326c
0fb25c9
f981784
733ad24
ab11691
065599f
1050da7
dbdf612
48a10fb
8000442
e55286e
6070337
a2ddd2f
5c9bf84
c08dce5
b2f4914
f6f5779
aead1d6
b43281c
39fbb05
522fae4
c153bbb
1e06653
06e18c9
5f1d700
08864e1
b28a423
0c81cca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import Foundation | ||
|
||
extension Numeric { | ||
var bytes: Data { | ||
return withUnsafePointer(to: self) { | ||
Data(bytes: $0, count: MemoryLayout.size(ofValue: self)) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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() { | ||
|
@@ -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. | ||
|
@@ -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 }) | ||
} | ||
|
||
|
@@ -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: | ||
|
@@ -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 { | ||
|
@@ -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?) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: | ||
|
@@ -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]) { | ||
|
@@ -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( | ||
|
@@ -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?) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
} | ||
} |
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) | ||
} | ||
} |
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
} | ||
} |
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) | ||
} |
There was a problem hiding this comment.
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.