From c15698acbbbeee25f249883653df1b060fcdea46 Mon Sep 17 00:00:00 2001 From: decanus Date: Fri, 11 Oct 2019 10:42:46 +0900 Subject: [PATCH 01/30] merged, adding some basic stuff to pass identity to transport --- Gemfile.lock | 99 ++++++++++++++++++ Sources/UB/Transports/Transport.swift | 5 +- docs/Classes.html | 2 +- docs/Classes/CoreBluetoothTransport.html | 2 +- docs/Classes/Node.html | 2 +- docs/Classes/Peer.html | 2 +- docs/Protocols.html | 2 +- docs/Protocols/NodeDelegate.html | 2 +- docs/Protocols/Transport.html | 2 +- docs/Protocols/TransportDelegate.html | 2 +- docs/Structs.html | 2 +- docs/Structs/Message.html | 2 +- docs/Typealiases.html | 2 +- .../Contents/Resources/Documents/Classes.html | 2 +- .../Classes/CoreBluetoothTransport.html | 2 +- .../Resources/Documents/Classes/Node.html | 2 +- .../Resources/Documents/Classes/Peer.html | 2 +- .../Resources/Documents/Protocols.html | 2 +- .../Documents/Protocols/NodeDelegate.html | 2 +- .../Documents/Protocols/Transport.html | 2 +- .../Protocols/TransportDelegate.html | 2 +- .../Contents/Resources/Documents/Structs.html | 2 +- .../Resources/Documents/Structs/Message.html | 2 +- .../Resources/Documents/Typealiases.html | 2 +- .../Contents/Resources/Documents/index.html | 2 +- docs/docsets/UB.tgz | Bin 47834 -> 47833 bytes docs/index.html | 2 +- 27 files changed, 127 insertions(+), 25 deletions(-) create mode 100644 Gemfile.lock diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..106964b --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,99 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.1) + activesupport (4.2.11.1) + i18n (~> 0.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + atomos (0.1.3) + claide (1.0.3) + cocoapods (1.7.5) + activesupport (>= 4.0.2, < 5) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.7.5) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 1.2.2, < 2.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-stats (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.3.1, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.6.6) + nap (~> 1.0) + ruby-macho (~> 1.4) + xcodeproj (>= 1.10.0, < 2.0) + cocoapods-core (1.7.5) + activesupport (>= 4.0.2, < 6) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + cocoapods-deintegrate (1.0.4) + cocoapods-downloader (1.2.2) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.0) + cocoapods-stats (1.1.0) + cocoapods-trunk (1.4.0) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.1.0) + colored2 (3.1.2) + concurrent-ruby (1.1.5) + escape (0.0.4) + ffi (1.11.1) + fourflusher (2.3.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + i18n (0.9.5) + concurrent-ruby (~> 1.0) + jazzy (0.11.1) + cocoapods (~> 1.5) + mustache (~> 1.1) + open4 + redcarpet (~> 3.4) + rouge (>= 2.0.6, < 4.0) + sassc (~> 2.1) + sqlite3 (~> 1.3) + xcinvoke (~> 0.3.0) + liferaft (0.0.6) + minitest (5.11.3) + molinillo (0.6.6) + mustache (1.1.0) + nanaimo (0.2.6) + nap (1.1.0) + netrc (0.11.0) + open4 (1.3.4) + redcarpet (3.5.0) + rouge (2.0.7) + ruby-macho (1.4.0) + sassc (2.2.1) + ffi (~> 1.9) + sqlite3 (1.4.1) + thread_safe (0.3.6) + tzinfo (1.2.5) + thread_safe (~> 0.1) + xcinvoke (0.3.0) + liferaft (~> 0.0.6) + xcodeproj (1.12.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.2.6) + xcpretty (0.3.0) + rouge (~> 2.0.7) + +PLATFORMS + ruby + +DEPENDENCIES + jazzy (= 0.11.1) + xcpretty + +BUNDLED WITH + 1.17.2 diff --git a/Sources/UB/Transports/Transport.swift b/Sources/UB/Transports/Transport.swift index 6950007..5c4153d 100644 --- a/Sources/UB/Transports/Transport.swift +++ b/Sources/UB/Transports/Transport.swift @@ -16,5 +16,8 @@ public protocol Transport { func send(message: Data, to: Addr) /// Listen implements a function to receive messages being sent to a node. - func listen() + /// + /// - Parameters: + /// - identity: The identity of the node. + func listen(identity _: UBID) } diff --git a/docs/Classes.html b/docs/Classes.html index 5b3641a..d276cc5 100644 --- a/docs/Classes.html +++ b/docs/Classes.html @@ -184,7 +184,7 @@

Declaration

diff --git a/docs/Classes/CoreBluetoothTransport.html b/docs/Classes/CoreBluetoothTransport.html index aa689f4..6803942 100644 --- a/docs/Classes/CoreBluetoothTransport.html +++ b/docs/Classes/CoreBluetoothTransport.html @@ -183,7 +183,7 @@

Parameters

diff --git a/docs/Classes/Node.html b/docs/Classes/Node.html index 01af726..072f09b 100644 --- a/docs/Classes/Node.html +++ b/docs/Classes/Node.html @@ -317,7 +317,7 @@

Parameters

diff --git a/docs/Classes/Peer.html b/docs/Classes/Peer.html index 6160c14..273544f 100644 --- a/docs/Classes/Peer.html +++ b/docs/Classes/Peer.html @@ -152,7 +152,7 @@

Declaration

diff --git a/docs/Protocols.html b/docs/Protocols.html index 8b2f3c5..6109e5e 100644 --- a/docs/Protocols.html +++ b/docs/Protocols.html @@ -184,7 +184,7 @@

Declaration

diff --git a/docs/Protocols/NodeDelegate.html b/docs/Protocols/NodeDelegate.html index fdac58a..e08a0d8 100644 --- a/docs/Protocols/NodeDelegate.html +++ b/docs/Protocols/NodeDelegate.html @@ -156,7 +156,7 @@

Parameters

diff --git a/docs/Protocols/Transport.html b/docs/Protocols/Transport.html index bece9f3..2921bbc 100644 --- a/docs/Protocols/Transport.html +++ b/docs/Protocols/Transport.html @@ -237,7 +237,7 @@

Declaration

diff --git a/docs/Protocols/TransportDelegate.html b/docs/Protocols/TransportDelegate.html index 9b81f15..0e78ee8 100644 --- a/docs/Protocols/TransportDelegate.html +++ b/docs/Protocols/TransportDelegate.html @@ -168,7 +168,7 @@

Parameters

diff --git a/docs/Structs.html b/docs/Structs.html index 1b19784..1f5df24 100644 --- a/docs/Structs.html +++ b/docs/Structs.html @@ -120,7 +120,7 @@

Declaration

diff --git a/docs/Structs/Message.html b/docs/Structs/Message.html index 1062476..10e750a 100644 --- a/docs/Structs/Message.html +++ b/docs/Structs/Message.html @@ -329,7 +329,7 @@

Parameters

diff --git a/docs/Typealiases.html b/docs/Typealiases.html index 2e5657e..a6da1e8 100644 --- a/docs/Typealiases.html +++ b/docs/Typealiases.html @@ -146,7 +146,7 @@

Declaration

diff --git a/docs/docsets/UB.docset/Contents/Resources/Documents/Classes.html b/docs/docsets/UB.docset/Contents/Resources/Documents/Classes.html index 5b3641a..d276cc5 100644 --- a/docs/docsets/UB.docset/Contents/Resources/Documents/Classes.html +++ b/docs/docsets/UB.docset/Contents/Resources/Documents/Classes.html @@ -184,7 +184,7 @@

Declaration

diff --git a/docs/docsets/UB.docset/Contents/Resources/Documents/Classes/CoreBluetoothTransport.html b/docs/docsets/UB.docset/Contents/Resources/Documents/Classes/CoreBluetoothTransport.html index aa689f4..6803942 100644 --- a/docs/docsets/UB.docset/Contents/Resources/Documents/Classes/CoreBluetoothTransport.html +++ b/docs/docsets/UB.docset/Contents/Resources/Documents/Classes/CoreBluetoothTransport.html @@ -183,7 +183,7 @@

Parameters

diff --git a/docs/docsets/UB.docset/Contents/Resources/Documents/Classes/Node.html b/docs/docsets/UB.docset/Contents/Resources/Documents/Classes/Node.html index 01af726..072f09b 100644 --- a/docs/docsets/UB.docset/Contents/Resources/Documents/Classes/Node.html +++ b/docs/docsets/UB.docset/Contents/Resources/Documents/Classes/Node.html @@ -317,7 +317,7 @@

Parameters

diff --git a/docs/docsets/UB.docset/Contents/Resources/Documents/Classes/Peer.html b/docs/docsets/UB.docset/Contents/Resources/Documents/Classes/Peer.html index 6160c14..273544f 100644 --- a/docs/docsets/UB.docset/Contents/Resources/Documents/Classes/Peer.html +++ b/docs/docsets/UB.docset/Contents/Resources/Documents/Classes/Peer.html @@ -152,7 +152,7 @@

Declaration

diff --git a/docs/docsets/UB.docset/Contents/Resources/Documents/Protocols.html b/docs/docsets/UB.docset/Contents/Resources/Documents/Protocols.html index 8b2f3c5..6109e5e 100644 --- a/docs/docsets/UB.docset/Contents/Resources/Documents/Protocols.html +++ b/docs/docsets/UB.docset/Contents/Resources/Documents/Protocols.html @@ -184,7 +184,7 @@

Declaration

diff --git a/docs/docsets/UB.docset/Contents/Resources/Documents/Protocols/NodeDelegate.html b/docs/docsets/UB.docset/Contents/Resources/Documents/Protocols/NodeDelegate.html index fdac58a..e08a0d8 100644 --- a/docs/docsets/UB.docset/Contents/Resources/Documents/Protocols/NodeDelegate.html +++ b/docs/docsets/UB.docset/Contents/Resources/Documents/Protocols/NodeDelegate.html @@ -156,7 +156,7 @@

Parameters

diff --git a/docs/docsets/UB.docset/Contents/Resources/Documents/Protocols/Transport.html b/docs/docsets/UB.docset/Contents/Resources/Documents/Protocols/Transport.html index bece9f3..2921bbc 100644 --- a/docs/docsets/UB.docset/Contents/Resources/Documents/Protocols/Transport.html +++ b/docs/docsets/UB.docset/Contents/Resources/Documents/Protocols/Transport.html @@ -237,7 +237,7 @@

Declaration

diff --git a/docs/docsets/UB.docset/Contents/Resources/Documents/Protocols/TransportDelegate.html b/docs/docsets/UB.docset/Contents/Resources/Documents/Protocols/TransportDelegate.html index 9b81f15..0e78ee8 100644 --- a/docs/docsets/UB.docset/Contents/Resources/Documents/Protocols/TransportDelegate.html +++ b/docs/docsets/UB.docset/Contents/Resources/Documents/Protocols/TransportDelegate.html @@ -168,7 +168,7 @@

Parameters

diff --git a/docs/docsets/UB.docset/Contents/Resources/Documents/Structs.html b/docs/docsets/UB.docset/Contents/Resources/Documents/Structs.html index 1b19784..1f5df24 100644 --- a/docs/docsets/UB.docset/Contents/Resources/Documents/Structs.html +++ b/docs/docsets/UB.docset/Contents/Resources/Documents/Structs.html @@ -120,7 +120,7 @@

Declaration

diff --git a/docs/docsets/UB.docset/Contents/Resources/Documents/Structs/Message.html b/docs/docsets/UB.docset/Contents/Resources/Documents/Structs/Message.html index 1062476..10e750a 100644 --- a/docs/docsets/UB.docset/Contents/Resources/Documents/Structs/Message.html +++ b/docs/docsets/UB.docset/Contents/Resources/Documents/Structs/Message.html @@ -329,7 +329,7 @@

Parameters

diff --git a/docs/docsets/UB.docset/Contents/Resources/Documents/Typealiases.html b/docs/docsets/UB.docset/Contents/Resources/Documents/Typealiases.html index 2e5657e..a6da1e8 100644 --- a/docs/docsets/UB.docset/Contents/Resources/Documents/Typealiases.html +++ b/docs/docsets/UB.docset/Contents/Resources/Documents/Typealiases.html @@ -146,7 +146,7 @@

Declaration

diff --git a/docs/docsets/UB.docset/Contents/Resources/Documents/index.html b/docs/docsets/UB.docset/Contents/Resources/Documents/index.html index b2c826b..a7825a2 100644 --- a/docs/docsets/UB.docset/Contents/Resources/Documents/index.html +++ b/docs/docsets/UB.docset/Contents/Resources/Documents/index.html @@ -134,7 +134,7 @@

License

diff --git a/docs/docsets/UB.tgz b/docs/docsets/UB.tgz index 7dbd5112d0c50821a064bd75b2a52083c973e526..2f233476607d1abe41af026231edbb3712e4e193 100644 GIT binary patch delta 45737 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 PEB^i;)|iOn0O$h%Ck`Zi diff --git a/docs/index.html b/docs/index.html index b2c826b..a7825a2 100644 --- a/docs/index.html +++ b/docs/index.html @@ -134,7 +134,7 @@

License

From 40ebd84d503cb8f4e8c55e186fbd3a34a86f0986 Mon Sep 17 00:00:00 2001 From: decanus Date: Mon, 14 Oct 2019 00:34:16 +0900 Subject: [PATCH 02/30] addition --- Sources/UB/Transports/CoreBluetoothTransport.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/UB/Transports/CoreBluetoothTransport.swift b/Sources/UB/Transports/CoreBluetoothTransport.swift index 53b226e..25e158b 100644 --- a/Sources/UB/Transports/CoreBluetoothTransport.swift +++ b/Sources/UB/Transports/CoreBluetoothTransport.swift @@ -100,7 +100,7 @@ extension CoreBluetoothTransport: Transport { } } - public func listen() { + public func listen(_ identity: UBID) { // @todo mark as listening, only turn on peripheral characteristic at this point, etc. } } From 6ea37572fb196525adbc716aad44bd1513c1dd79 Mon Sep 17 00:00:00 2001 From: decanus Date: Wed, 16 Oct 2019 07:59:03 +0200 Subject: [PATCH 03/30] started working on identity --- Sources/UB/Node.swift | 61 ++++++++++++------- Sources/UB/Peer.swift | 7 ++- Sources/UB/Transports/Transport.swift | 3 - Sources/UB/Transports/TransportDelegate.swift | 15 +++++ 4 files changed, 61 insertions(+), 25 deletions(-) diff --git a/Sources/UB/Node.swift b/Sources/UB/Node.swift index e72c4b6..cc23209 100644 --- a/Sources/UB/Node.swift +++ b/Sources/UB/Node.swift @@ -8,6 +8,9 @@ public class Node { /// The known transports for the node. public private(set) var transports = [String: Transport]() + /// The known peers for a node. + public private(set) var peers = [Addr: Peer]() + /// The nodes delegate. public weak var delegate: NodeDelegate? @@ -27,7 +30,7 @@ public class Node { transports[id] = transport transports[id]?.delegate = self - transport.listen() + transport.listen(identity: UBID(repeating: 0, count: 0)) // @TODO } /// Removes a transport from the list of known transports. @@ -55,29 +58,31 @@ public class Node { 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: data, to: message.recipient) - } - } - - // what this does is send a message to anyone that implements a specific service - if message.service.count != 0 { - let filtered = peers.filter { $0.services.contains { $0 == message.service } } - if filtered.count > 0 { - let sends = flood(message, data: data, transport: transport, peers: filtered) - if sends > 0 { - return - } + if message.recipient.count != 0 { + if let peer = peers[message.recipient] { + // @todo ensure we actually had > 0 transports to send to. + return peer.transports.forEach { id, addr in + guard let transport = transports[id] else { return } + transport.send(message: data, to: addr) } } - _ = flood(message, data: data, transport: transport, peers: peers) } + + // @todo figure this out +// transports.forEach { _, transport in +// let peers = transport.peers +// // what this does is send a message to anyone that implements a specific service +// if message.service.count != 0 { +// let filtered = peers.filter { $0.services.contains { $0 == message.service } } +// if filtered.count > 0 { +// let sends = flood(message, data: data, transport: transport, peers: filtered) +// if sends > 0 { +// return +// } +// } +// } +// _ = flood(message, data: data, transport: transport, peers: peers) +// } } private func flood(_ message: Message, data: Data, transport: Transport, peers: [Peer]) -> Int { @@ -97,6 +102,7 @@ public class Node { // @todo create a message send loop with retransmissions and shit } +c /// :nodoc: extension Node: TransportDelegate { public func transport(_: Transport, didReceiveData data: Data, from: Addr) { @@ -113,4 +119,17 @@ extension Node: TransportDelegate { delegate?.node(self, didReceiveMessage: Message(protobuf: packet, from: from)) } + + public func transport(_ transport: Transport, didConnectToPeer id: Addr, withAddr addr: Addr) { + guard let peer = peers[id] else { + peer = Peer(id: id, services: [UBID]()) + peers[id] = peer + } cx12vbh3 jn4m + + peer.transports[String(describing: transport)] = addr + } + + public func transport(_ transport: Transport, didDisconnectFromPeer id: Addr) { + peers[peer].transports.removeValue(forKey: String(describing: transport)) + } } diff --git a/Sources/UB/Peer.swift b/Sources/UB/Peer.swift index 8c94bb9..50b5ae5 100644 --- a/Sources/UB/Peer.swift +++ b/Sources/UB/Peer.swift @@ -2,14 +2,19 @@ import Foundation // @todo clean this up properly, currently very rough for testing purposes. +// @todo set transport addresses, allowing a node to select which transports to send on. + /// Represents the nodes a transport can communicate with. public class Peer { - /// The peers id. + /// The peers identifier. public let id: Addr /// The services a peer knows. public let services: [UBID] + /// A list of peer addresses for a given transport. + public var transports = [String: Addr]() + /// Initializes a peer with a specified id and list of known services. /// /// - Parameters: diff --git a/Sources/UB/Transports/Transport.swift b/Sources/UB/Transports/Transport.swift index 5c4153d..042d378 100644 --- a/Sources/UB/Transports/Transport.swift +++ b/Sources/UB/Transports/Transport.swift @@ -5,9 +5,6 @@ public protocol Transport { /// The transports delegate. var delegate: TransportDelegate? { get set } - /// The peers a specific transport can send messages to. - var peers: [Peer] { get } - /// Send implements a function to send messages between nodes using the transport. /// /// - Parameters: diff --git a/Sources/UB/Transports/TransportDelegate.swift b/Sources/UB/Transports/TransportDelegate.swift index 7880d63..0524d17 100644 --- a/Sources/UB/Transports/TransportDelegate.swift +++ b/Sources/UB/Transports/TransportDelegate.swift @@ -9,4 +9,19 @@ public protocol TransportDelegate: AnyObject { /// - data: The received data. /// - from: The peer from which the data was received. func transport(_ transport: Transport, didReceiveData data: Data, from: Addr) + + /// This method is called when a transport connects to a specific peer. + /// + /// - Parameters: + /// - transport: The transport that connected to a peer. + /// - peer: The peer identifier. + /// - addr: The peer address. + func transport(_ transport: Transport, didConnectToPeer peer: Addr, withAddr addr: Addr) + + /// This method is called when a transport disconnects from a specific peer. + /// + /// - Parameters: + /// - transport: The transport that disconnected from a peer. + /// - peer: The peer identifier. + func transport(_ transport: Transport, didDisconnectFromPeer peer: Addr) } From 5dcf8ac60c493322868203bda96f76a60f3d05b6 Mon Sep 17 00:00:00 2001 From: Dean Eigenmann Date: Wed, 16 Oct 2019 09:03:54 +0200 Subject: [PATCH 04/30] Update Node.swift --- Sources/UB/Node.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/UB/Node.swift b/Sources/UB/Node.swift index cc23209..d0cc3e4 100644 --- a/Sources/UB/Node.swift +++ b/Sources/UB/Node.swift @@ -124,7 +124,7 @@ extension Node: TransportDelegate { guard let peer = peers[id] else { peer = Peer(id: id, services: [UBID]()) peers[id] = peer - } cx12vbh3 jn4m + } peer.transports[String(describing: transport)] = addr } From bff46dd7af2282213e39a55dcbeb46e7c2815e4a Mon Sep 17 00:00:00 2001 From: decanus <7621705+decanus@users.noreply.github.com> Date: Wed, 16 Oct 2019 15:52:48 +0200 Subject: [PATCH 05/30] fixes --- Sources/UB/Node.swift | 11 ++++++----- Sources/UB/Transports/CoreBluetoothTransport.swift | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Sources/UB/Node.swift b/Sources/UB/Node.swift index d0cc3e4..74de0db 100644 --- a/Sources/UB/Node.swift +++ b/Sources/UB/Node.swift @@ -102,7 +102,6 @@ public class Node { // @todo create a message send loop with retransmissions and shit } -c /// :nodoc: extension Node: TransportDelegate { public func transport(_: Transport, didReceiveData data: Data, from: Addr) { @@ -121,15 +120,17 @@ extension Node: TransportDelegate { } public func transport(_ transport: Transport, didConnectToPeer id: Addr, withAddr addr: Addr) { - guard let peer = peers[id] else { - peer = Peer(id: id, services: [UBID]()) - peers[id] = peer + if peers[id] == nil { + peers[id] = Peer(id: id, services: [UBID]()) } + guard let peer = peers[id] else { return } + peer.transports[String(describing: transport)] = addr } public func transport(_ transport: Transport, didDisconnectFromPeer id: Addr) { - peers[peer].transports.removeValue(forKey: String(describing: transport)) + guard let peer = peers[id] else { return } + peer.transports.removeValue(forKey: String(describing: transport)) } } diff --git a/Sources/UB/Transports/CoreBluetoothTransport.swift b/Sources/UB/Transports/CoreBluetoothTransport.swift index 25e158b..684f093 100644 --- a/Sources/UB/Transports/CoreBluetoothTransport.swift +++ b/Sources/UB/Transports/CoreBluetoothTransport.swift @@ -100,7 +100,7 @@ extension CoreBluetoothTransport: Transport { } } - public func listen(_ identity: UBID) { + public func listen(identity: UBID) { // @todo mark as listening, only turn on peripheral characteristic at this point, etc. } } From 13151a591eee3e7cddeb98d0041a5970a34a637c Mon Sep 17 00:00:00 2001 From: Dean Eigenmann Date: Wed, 16 Oct 2019 15:55:46 +0200 Subject: [PATCH 06/30] Update Node.swift --- Sources/UB/Node.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Sources/UB/Node.swift b/Sources/UB/Node.swift index d0cc3e4..74de0db 100644 --- a/Sources/UB/Node.swift +++ b/Sources/UB/Node.swift @@ -102,7 +102,6 @@ public class Node { // @todo create a message send loop with retransmissions and shit } -c /// :nodoc: extension Node: TransportDelegate { public func transport(_: Transport, didReceiveData data: Data, from: Addr) { @@ -121,15 +120,17 @@ extension Node: TransportDelegate { } public func transport(_ transport: Transport, didConnectToPeer id: Addr, withAddr addr: Addr) { - guard let peer = peers[id] else { - peer = Peer(id: id, services: [UBID]()) - peers[id] = peer + if peers[id] == nil { + peers[id] = Peer(id: id, services: [UBID]()) } + guard let peer = peers[id] else { return } + peer.transports[String(describing: transport)] = addr } public func transport(_ transport: Transport, didDisconnectFromPeer id: Addr) { - peers[peer].transports.removeValue(forKey: String(describing: transport)) + guard let peer = peers[id] else { return } + peer.transports.removeValue(forKey: String(describing: transport)) } } From bef95a905d3c3dfe7479eac5a630f527e21bd06b Mon Sep 17 00:00:00 2001 From: Dean Eigenmann Date: Wed, 16 Oct 2019 15:56:43 +0200 Subject: [PATCH 07/30] Update CoreBluetoothTransport.swift --- Sources/UB/Transports/CoreBluetoothTransport.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/UB/Transports/CoreBluetoothTransport.swift b/Sources/UB/Transports/CoreBluetoothTransport.swift index 25e158b..684f093 100644 --- a/Sources/UB/Transports/CoreBluetoothTransport.swift +++ b/Sources/UB/Transports/CoreBluetoothTransport.swift @@ -100,7 +100,7 @@ extension CoreBluetoothTransport: Transport { } } - public func listen(_ identity: UBID) { + public func listen(identity: UBID) { // @todo mark as listening, only turn on peripheral characteristic at this point, etc. } } From 82a0829c0b5fa775571bbcb667b5440d4f43db01 Mon Sep 17 00:00:00 2001 From: Dean Eigenmann Date: Wed, 16 Oct 2019 16:03:11 +0200 Subject: [PATCH 08/30] Update Transport.swift --- Sources/UB/Transports/Transport.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/UB/Transports/Transport.swift b/Sources/UB/Transports/Transport.swift index 042d378..7c3972e 100644 --- a/Sources/UB/Transports/Transport.swift +++ b/Sources/UB/Transports/Transport.swift @@ -16,5 +16,5 @@ public protocol Transport { /// /// - Parameters: /// - identity: The identity of the node. - func listen(identity _: UBID) + func listen(identity: UBID) } From 168ecd848a6c02e280d1d2fd842411c721c00560 Mon Sep 17 00:00:00 2001 From: Dean Eigenmann Date: Wed, 16 Oct 2019 16:35:10 +0200 Subject: [PATCH 09/30] Update Transport.swift --- Tests/UBTests/stubs/Transport.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/UBTests/stubs/Transport.swift b/Tests/UBTests/stubs/Transport.swift index cf569c1..c1ca89a 100644 --- a/Tests/UBTests/stubs/Transport.swift +++ b/Tests/UBTests/stubs/Transport.swift @@ -1,7 +1,7 @@ import Foundation import UB -class Transport: UB.Transport { +class Transport: Transport { weak var delegate: TransportDelegate? private(set) var sent: [(Data, Addr)] = [] @@ -12,5 +12,5 @@ class Transport: UB.Transport { sent.append((message, to)) } - func listen() {} + func listen(identity: UBID) {} } From 58ffd8497a697a4ae8bb93b46b4bcb2179e169b5 Mon Sep 17 00:00:00 2001 From: Dean Eigenmann Date: Wed, 16 Oct 2019 16:47:16 +0200 Subject: [PATCH 10/30] Update Transport.swift --- Tests/UBTests/stubs/Transport.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/UBTests/stubs/Transport.swift b/Tests/UBTests/stubs/Transport.swift index c1ca89a..3d54859 100644 --- a/Tests/UBTests/stubs/Transport.swift +++ b/Tests/UBTests/stubs/Transport.swift @@ -7,6 +7,8 @@ class Transport: Transport { private(set) var sent: [(Data, Addr)] = [] var peers: [Peer] = [] + + init() { } func send(message: Data, to: Addr) { sent.append((message, to)) From 0ea8473a562864c1a4bd7eda688656cde07ea49b Mon Sep 17 00:00:00 2001 From: Dean Eigenmann Date: Wed, 16 Oct 2019 17:02:28 +0200 Subject: [PATCH 11/30] Update Transport.swift --- Tests/UBTests/stubs/Transport.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/UBTests/stubs/Transport.swift b/Tests/UBTests/stubs/Transport.swift index 3d54859..56af080 100644 --- a/Tests/UBTests/stubs/Transport.swift +++ b/Tests/UBTests/stubs/Transport.swift @@ -1,7 +1,7 @@ import Foundation import UB -class Transport: Transport { +class Transport: UB.Transport { weak var delegate: TransportDelegate? private(set) var sent: [(Data, Addr)] = [] From 9c621f9a48627bb6c0284af9cd8b20c703610899 Mon Sep 17 00:00:00 2001 From: decanus <7621705+decanus@users.noreply.github.com> Date: Mon, 21 Oct 2019 21:09:42 +0200 Subject: [PATCH 12/30] Curve25519 keys --- README.md | 2 +- Sources/UB/Node.swift | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 21f9e0d..cfe16ca 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Using the UB within your own project is kept simple. Initialize a `Node`, and as ```swift import UB -let node = Node() +let node = Node(key: Curve25519.Signing.PrivateKey()) node.delegate = self node.add(transport: CoreBluetoothTransport()) diff --git a/Sources/UB/Node.swift b/Sources/UB/Node.swift index 74de0db..eace2f4 100644 --- a/Sources/UB/Node.swift +++ b/Sources/UB/Node.swift @@ -1,5 +1,6 @@ import Foundation import SwiftProtobuf +import CryptoKit // @todo figure out architecture to support new forwarding algorithm. @@ -14,8 +15,16 @@ public class Node { /// The nodes delegate. public weak var delegate: NodeDelegate? + /// The nodes private key. + private let key: Curve25519.Signing.PrivateKey + /// Initializes a node. - public init() {} + /// + /// - Parameters: + /// - key: The private key for the node. + public init(key: Curve25519.Signing.PrivateKey) { + self.key = key + } /// Adds a new transport to the list of known transports. /// @@ -30,7 +39,7 @@ public class Node { transports[id] = transport transports[id]?.delegate = self - transport.listen(identity: UBID(repeating: 0, count: 0)) // @TODO + transport.listen(identity: UBID(key.publicKey.rawRepresentation)) } /// Removes a transport from the list of known transports. From 7192573580d4688f542752938035d1c00cca3352 Mon Sep 17 00:00:00 2001 From: decanus <7621705+decanus@users.noreply.github.com> Date: Mon, 21 Oct 2019 22:54:58 +0200 Subject: [PATCH 13/30] split --- .../Transports/CoreBluetoothTransport.swift | 49 +++++++++++++------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/Sources/UB/Transports/CoreBluetoothTransport.swift b/Sources/UB/Transports/CoreBluetoothTransport.swift index 684f093..63cb1a9 100644 --- a/Sources/UB/Transports/CoreBluetoothTransport.swift +++ b/Sources/UB/Transports/CoreBluetoothTransport.swift @@ -3,6 +3,7 @@ import Foundation /// CoreBluetoothTransport is used to send and receive message over Bluetooth public class CoreBluetoothTransport: NSObject { + /// :nodoc: public weak var delegate: TransportDelegate? @@ -32,6 +33,12 @@ public class CoreBluetoothTransport: NSObject { permissions: [.writeable, .readable] ) + private enum State { + case off, listening + } + + private var state = State.off + // make this nicer, we need this cause we need a reference to the peripheral? private var perp: CBPeripheral? private var centrals = [Addr: CBCentral]() @@ -101,22 +108,23 @@ extension CoreBluetoothTransport: Transport { } public func listen(identity: UBID) { - // @todo mark as listening, only turn on peripheral characteristic at this point, etc. + state = .listening + + if peripheralManager.state == .poweredOn { + startAdvertising() + } + + if centralManager.state == .poweredOn { + startScanning() + } } } /// :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], - ]) + if peripheral.state == .poweredOn && state == .listening { + startAdvertising() } } @@ -150,16 +158,25 @@ extension CoreBluetoothTransport: CBPeripheralManagerDelegate { centrals.removeValue(forKey: id) peers.removeAll(where: { $0.id == id }) } + + fileprivate func startAdvertising() { + let service = CBMutableService(type: CoreBluetoothTransport.ubServiceUUID, primary: true) + + service.characteristics = [CoreBluetoothTransport.characteristic] + peripheralManager.add(service) + + peripheralManager.startAdvertising([ + CBAdvertisementDataServiceUUIDsKey: [CoreBluetoothTransport.ubServiceUUID], + ]) + } } /// :nodoc: extension CoreBluetoothTransport: CBCentralManagerDelegate { public func centralManagerDidUpdateState(_ central: CBCentralManager) { - if central.state == .poweredOn { - centralManager.scanForPeripherals(withServices: [CoreBluetoothTransport.ubServiceUUID]) + if central.state == .poweredOn && state == .listening { + startScanning() } - - // @todo handling for other states } public func centralManager( @@ -180,6 +197,10 @@ extension CoreBluetoothTransport: CBCentralManagerDelegate { public func centralManager(_: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error _: Error?) { remove(peer: Addr(peripheral.identifier.bytes)) } + + fileprivate func startScanning() { + centralManager.scanForPeripherals(withServices: [CoreBluetoothTransport.ubServiceUUID]) + } } /// :nodoc: From 17a0acdc7c0a7543412cbc136f35ce2805dd7ba0 Mon Sep 17 00:00:00 2001 From: decanus <7621705+decanus@users.noreply.github.com> Date: Mon, 21 Oct 2019 23:02:11 +0200 Subject: [PATCH 14/30] started --- .../Transports/CoreBluetoothTransport.swift | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Sources/UB/Transports/CoreBluetoothTransport.swift b/Sources/UB/Transports/CoreBluetoothTransport.swift index a5b0436..4f3afa4 100644 --- a/Sources/UB/Transports/CoreBluetoothTransport.swift +++ b/Sources/UB/Transports/CoreBluetoothTransport.swift @@ -25,10 +25,17 @@ public class CoreBluetoothTransport: NSObject { private static let ubServiceUUID = CBUUID(string: "BEA3B031-76FB-4889-B3C7-000000000000") - private static let receiveCharacteristic = CBMutableCharacteristic( + private static let identityCharacteristic = CBMutableCharacteristic( type: CBUUID(string: "BEA3B031-76FB-4889-B3C7-000000000001"), properties: [.read, .writeWithoutResponse, .notify], value: nil, + permissions: [.writeable, .readable] + ) + + private static let receiveCharacteristic = CBMutableCharacteristic( + type: CBUUID(string: "BEA3B031-76FB-4889-B3C7-000000000002"), + properties: [.read, .writeWithoutResponse, .notify], + value: nil, permissions: [.writeable, .readable] ) @@ -109,6 +116,8 @@ extension CoreBluetoothTransport: Transport { public func listen(identity: UBID) { state = .listening + identityCharacteristic.value = identity + if peripheralManager.state == .poweredOn { startAdvertising() } @@ -161,7 +170,11 @@ extension CoreBluetoothTransport: CBPeripheralManagerDelegate { fileprivate func startAdvertising() { let service = CBMutableService(type: CoreBluetoothTransport.ubServiceUUID, primary: true) - service.characteristics = [CoreBluetoothTransport.receiveCharacteristic] + service.characteristics = [ + CoreBluetoothTransport.identityCharacteristic, + CoreBluetoothTransport.receiveCharacteristic, + ] + peripheral.add(service) peripheral.startAdvertising([ @@ -206,7 +219,10 @@ extension CoreBluetoothTransport: CBCentralManagerDelegate { extension CoreBluetoothTransport: CBPeripheralDelegate { public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices _: Error?) { if let service = peripheral.services?.first(where: { $0.uuid == CoreBluetoothTransport.ubServiceUUID }) { - peripheral.discoverCharacteristics([CoreBluetoothTransport.receiveCharacteristic.uuid], for: service) + peripheral.discoverCharacteristics( + [CoreBluetoothTransport.identityCharacteristic.uuid, CoreBluetoothTransport.receiveCharacteristic.uuid], + for: service + ) } } From 91b5a9b23aba8c7d002df74c13882ebea8479dcb Mon Sep 17 00:00:00 2001 From: decanus <7621705+decanus@users.noreply.github.com> Date: Mon, 21 Oct 2019 23:11:06 +0200 Subject: [PATCH 15/30] updates --- Package.swift | 4 +-- .../Transports/CoreBluetoothTransport.swift | 26 ++++++++++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/Package.swift b/Package.swift index 5b35f6a..801b4a0 100644 --- a/Package.swift +++ b/Package.swift @@ -1,11 +1,11 @@ -// swift-tools-version:5.0 +// swift-tools-version:5.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "UB", platforms: [ - .macOS(.v10_13), + .macOS(.v10_15), ], products: [ // Products define the executables and libraries produced by a package, and make them visible to other packages. diff --git a/Sources/UB/Transports/CoreBluetoothTransport.swift b/Sources/UB/Transports/CoreBluetoothTransport.swift index 4f3afa4..5c9bafa 100644 --- a/Sources/UB/Transports/CoreBluetoothTransport.swift +++ b/Sources/UB/Transports/CoreBluetoothTransport.swift @@ -25,7 +25,7 @@ public class CoreBluetoothTransport: NSObject { private static let ubServiceUUID = CBUUID(string: "BEA3B031-76FB-4889-B3C7-000000000000") - private static let identityCharacteristic = CBMutableCharacteristic( + private var identityCharacteristic = CBMutableCharacteristic( type: CBUUID(string: "BEA3B031-76FB-4889-B3C7-000000000001"), properties: [.read, .writeWithoutResponse, .notify], value: nil, @@ -116,7 +116,7 @@ extension CoreBluetoothTransport: Transport { public func listen(identity: UBID) { state = .listening - identityCharacteristic.value = identity + identityCharacteristic.value = Data(identity) if peripheralManager.state == .poweredOn { startAdvertising() @@ -140,7 +140,12 @@ extension CoreBluetoothTransport: CBPeripheralManagerDelegate { for request in requests { guard let data = request.value else { // @todo - return + continue + } + + if request.characteristic.uuid == identityCharacteristic.uuid { + delegate?.transport(self, didConnectToPeer: Addr(data), withAddr: Addr(request.central.identifier.bytes)) + continue } delegate?.transport(self, didReceiveData: data, from: Addr(request.central.identifier.bytes)) @@ -171,13 +176,13 @@ extension CoreBluetoothTransport: CBPeripheralManagerDelegate { let service = CBMutableService(type: CoreBluetoothTransport.ubServiceUUID, primary: true) service.characteristics = [ - CoreBluetoothTransport.identityCharacteristic, + identityCharacteristic, CoreBluetoothTransport.receiveCharacteristic, ] - peripheral.add(service) + peripheralManager.add(service) - peripheral.startAdvertising([ + peripheralManager.startAdvertising([ CBAdvertisementDataServiceUUIDsKey: [CoreBluetoothTransport.ubServiceUUID], ]) } @@ -220,7 +225,7 @@ extension CoreBluetoothTransport: CBPeripheralDelegate { public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices _: Error?) { if let service = peripheral.services?.first(where: { $0.uuid == CoreBluetoothTransport.ubServiceUUID }) { peripheral.discoverCharacteristics( - [CoreBluetoothTransport.identityCharacteristic.uuid, CoreBluetoothTransport.receiveCharacteristic.uuid], + [identityCharacteristic.uuid, CoreBluetoothTransport.receiveCharacteristic.uuid], for: service ) } @@ -237,6 +242,13 @@ extension CoreBluetoothTransport: CBPeripheralDelegate { } let characteristics = service.characteristics + + if let identity = characteristics?.first(where: { $0.uuid == identityCharacteristic.uuid }) { + if let data = identity.value { + delegate?.transport(self, didConnectToPeer: Addr(data), withAddr: id) + } + } + if let char = characteristics?.first(where: { $0.uuid == CoreBluetoothTransport.receiveCharacteristic.uuid }) { peripherals[id] = (peripheral, char) peripherals[id]?.peripheral.setNotifyValue(true, for: char) From a0227228ed3f3ba822f7eb893a798fb2d1e9505e Mon Sep 17 00:00:00 2001 From: decanus <7621705+decanus@users.noreply.github.com> Date: Mon, 21 Oct 2019 23:31:37 +0200 Subject: [PATCH 16/30] large changes, could lead to race conditions --- .../Transports/CoreBluetoothTransport.swift | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/Sources/UB/Transports/CoreBluetoothTransport.swift b/Sources/UB/Transports/CoreBluetoothTransport.swift index 5c9bafa..0de08cf 100644 --- a/Sources/UB/Transports/CoreBluetoothTransport.swift +++ b/Sources/UB/Transports/CoreBluetoothTransport.swift @@ -8,7 +8,7 @@ public class CoreBluetoothTransport: NSObject { public weak var delegate: TransportDelegate? /// :nodoc: - public fileprivate(set) var peers = [Peer]() + public fileprivate(set) var peers = [Addr: Addr]() private let centralManager: CBCentralManager private let peripheralManager: CBPeripheralManager @@ -73,7 +73,7 @@ public class CoreBluetoothTransport: NSObject { private func remove(peer: Addr) { peripherals.removeValue(forKey: peer) - peers.removeAll(where: { $0.id == peer }) + peers.removeValue(forKey: peer) } private func add(central: CBCentral) { @@ -85,11 +85,7 @@ public class CoreBluetoothTransport: NSObject { centrals[id] = central - if peers.filter({ $0.id == id }).count != 0 { - return - } - - peers.append(Peer(id: id, services: [UBID]())) + // @todo figure out identity for centrals } } @@ -143,12 +139,17 @@ extension CoreBluetoothTransport: CBPeripheralManagerDelegate { continue } + let id = Addr(request.central.identifier.bytes) + if request.characteristic.uuid == identityCharacteristic.uuid { - delegate?.transport(self, didConnectToPeer: Addr(data), withAddr: Addr(request.central.identifier.bytes)) + let addr = Addr(data) + peers[id] = addr + delegate?.transport(self, didConnectToPeer: addr, withAddr: id) continue } - delegate?.transport(self, didReceiveData: data, from: Addr(request.central.identifier.bytes)) + guard let peer = peers[id] else { continue } + delegate?.transport(self, didReceiveData: data, from: peer) add(central: request.central) } } @@ -169,7 +170,7 @@ extension CoreBluetoothTransport: CBPeripheralManagerDelegate { // @todo check that this is the characteristic let id = Addr(central.identifier.bytes) centrals.removeValue(forKey: id) - peers.removeAll(where: { $0.id == id }) + peers.removeValue(forKey: id) } fileprivate func startAdvertising() { @@ -241,24 +242,23 @@ extension CoreBluetoothTransport: CBPeripheralDelegate { return } - let characteristics = service.characteristics - - if let identity = characteristics?.first(where: { $0.uuid == identityCharacteristic.uuid }) { - if let data = identity.value { - delegate?.transport(self, didConnectToPeer: Addr(data), withAddr: id) + guard let characteristics = service.characteristics else { return } + for characteristic in characteristics { + if characteristic.uuid == identityCharacteristic.uuid { + if let data = characteristic.value { + let addr = Addr(data) + peers[id] = addr; + delegate?.transport(self, didConnectToPeer: addr, withAddr: id) + } } - } - - if let char = characteristics?.first(where: { $0.uuid == CoreBluetoothTransport.receiveCharacteristic.uuid }) { - 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. - if peers.filter({ $0.id == id }).count != 0 { - return + if characteristic.uuid == CoreBluetoothTransport.receiveCharacteristic.uuid { + peripherals[id] = (peripheral, characteristic) } + } - peers.append(Peer(id: id, services: [UBID]())) + if peers[id] != nil, let characteristic = peripherals[id]?.characteristic { + peripherals[id]?.peripheral.setNotifyValue(true, for: characteristic) } } @@ -273,9 +273,8 @@ extension CoreBluetoothTransport: CBPeripheralDelegate { didUpdateValueFor characteristic: CBCharacteristic, error _: Error? ) { - guard let value = characteristic.value else { return } - - delegate?.transport(self, didReceiveData: value, from: Addr(peripheral.identifier.bytes)) + guard let value = characteristic.value, let peer = peers[Addr(peripheral.identifier.bytes)] else { return } + delegate?.transport(self, didReceiveData: value, from: peer) } public func peripheral( From 85baf0b619a1ec755296f821574d92279cc1acb4 Mon Sep 17 00:00:00 2001 From: decanus <7621705+decanus@users.noreply.github.com> Date: Mon, 21 Oct 2019 23:34:47 +0200 Subject: [PATCH 17/30] code style fixes --- Sources/UB/Transports/CoreBluetoothTransport.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/UB/Transports/CoreBluetoothTransport.swift b/Sources/UB/Transports/CoreBluetoothTransport.swift index 0de08cf..0e2191c 100644 --- a/Sources/UB/Transports/CoreBluetoothTransport.swift +++ b/Sources/UB/Transports/CoreBluetoothTransport.swift @@ -29,7 +29,7 @@ public class CoreBluetoothTransport: NSObject { type: CBUUID(string: "BEA3B031-76FB-4889-B3C7-000000000001"), properties: [.read, .writeWithoutResponse, .notify], value: nil, - permissions: [.writeable, .readable] + permissions: [.writeable, .readable] ) private static let receiveCharacteristic = CBMutableCharacteristic( @@ -247,7 +247,7 @@ extension CoreBluetoothTransport: CBPeripheralDelegate { if characteristic.uuid == identityCharacteristic.uuid { if let data = characteristic.value { let addr = Addr(data) - peers[id] = addr; + peers[id] = addr delegate?.transport(self, didConnectToPeer: addr, withAddr: id) } } From f7a737658c069a4578620c6f3dcec88800c2adb9 Mon Sep 17 00:00:00 2001 From: decanus <7621705+decanus@users.noreply.github.com> Date: Mon, 21 Oct 2019 23:38:22 +0200 Subject: [PATCH 18/30] removed --- Sources/UB/Transports/CoreBluetoothTransport.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sources/UB/Transports/CoreBluetoothTransport.swift b/Sources/UB/Transports/CoreBluetoothTransport.swift index 0e2191c..fa520bb 100644 --- a/Sources/UB/Transports/CoreBluetoothTransport.swift +++ b/Sources/UB/Transports/CoreBluetoothTransport.swift @@ -84,8 +84,6 @@ public class CoreBluetoothTransport: NSObject { } centrals[id] = central - - // @todo figure out identity for centrals } } From 471fc140764eb1c7e9dc5334b4ffffb5a66c5141 Mon Sep 17 00:00:00 2001 From: decanus <7621705+decanus@users.noreply.github.com> Date: Mon, 21 Oct 2019 23:41:59 +0200 Subject: [PATCH 19/30] updates --- .swift-version | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.swift-version b/.swift-version index 819e07a..a75b92f 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -5.0 +5.1 diff --git a/README.md b/README.md index cfe16ca..8354671 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ UB.swift is the swift implementation of the Ultralight Beam protocol, its primar ## Requirements - **iOS 9** or later -- **OSX 10.13** or later -- **Swift 5.0** or later +- **OSX 10.15** or later +- **Swift 5.1** or later ## Installation From c66ab44a064fff4ea06f1857a01118abd8659c8a Mon Sep 17 00:00:00 2001 From: decanus <7621705+decanus@users.noreply.github.com> Date: Mon, 21 Oct 2019 23:52:27 +0200 Subject: [PATCH 20/30] updated --- UB.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UB.podspec b/UB.podspec index 4222c76..6b0340f 100644 --- a/UB.podspec +++ b/UB.podspec @@ -7,8 +7,8 @@ Pod::Spec.new do |spec| spec.source = { :git => 'https://github.com/ultralight-beam/UB.swift.git', :tag => 'v0.2.0'} spec.source_files = 'Sources/UB/**/*.swift' spec.summary = 'Swift implementation of the Ultralight Beam protocol' - spec.swift_version = '5.0' - spec.ios.deployment_target = '9.0' + spec.swift_version = '5.1' + spec.ios.deployment_target = '13.0' spec.osx.deployment_target = '10.13' spec.dependency 'SwiftProtobuf' end From 735fd8e0bded7df0db14f0dbd82f1fce54c50499 Mon Sep 17 00:00:00 2001 From: decanus <7621705+decanus@users.noreply.github.com> Date: Tue, 22 Oct 2019 00:08:12 +0200 Subject: [PATCH 21/30] fixes --- .../Transports/CoreBluetoothTransport.swift | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/Sources/UB/Transports/CoreBluetoothTransport.swift b/Sources/UB/Transports/CoreBluetoothTransport.swift index fa520bb..2332ff5 100644 --- a/Sources/UB/Transports/CoreBluetoothTransport.swift +++ b/Sources/UB/Transports/CoreBluetoothTransport.swift @@ -25,7 +25,7 @@ public class CoreBluetoothTransport: NSObject { private static let ubServiceUUID = CBUUID(string: "BEA3B031-76FB-4889-B3C7-000000000000") - private var identityCharacteristic = CBMutableCharacteristic( + private static let identityCharacteristic = CBMutableCharacteristic( type: CBUUID(string: "BEA3B031-76FB-4889-B3C7-000000000001"), properties: [.read, .writeWithoutResponse, .notify], value: nil, @@ -110,8 +110,6 @@ extension CoreBluetoothTransport: Transport { public func listen(identity: UBID) { state = .listening - identityCharacteristic.value = Data(identity) - if peripheralManager.state == .poweredOn { startAdvertising() } @@ -139,7 +137,7 @@ extension CoreBluetoothTransport: CBPeripheralManagerDelegate { let id = Addr(request.central.identifier.bytes) - if request.characteristic.uuid == identityCharacteristic.uuid { + if request.characteristic.uuid == CoreBluetoothTransport.identityCharacteristic.uuid { let addr = Addr(data) peers[id] = addr delegate?.transport(self, didConnectToPeer: addr, withAddr: id) @@ -175,7 +173,7 @@ extension CoreBluetoothTransport: CBPeripheralManagerDelegate { let service = CBMutableService(type: CoreBluetoothTransport.ubServiceUUID, primary: true) service.characteristics = [ - identityCharacteristic, + CoreBluetoothTransport.identityCharacteristic, CoreBluetoothTransport.receiveCharacteristic, ] @@ -224,7 +222,7 @@ extension CoreBluetoothTransport: CBPeripheralDelegate { public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices _: Error?) { if let service = peripheral.services?.first(where: { $0.uuid == CoreBluetoothTransport.ubServiceUUID }) { peripheral.discoverCharacteristics( - [identityCharacteristic.uuid, CoreBluetoothTransport.receiveCharacteristic.uuid], + [CoreBluetoothTransport.identityCharacteristic.uuid, CoreBluetoothTransport.receiveCharacteristic.uuid], for: service ) } @@ -242,12 +240,8 @@ extension CoreBluetoothTransport: CBPeripheralDelegate { guard let characteristics = service.characteristics else { return } for characteristic in characteristics { - if characteristic.uuid == identityCharacteristic.uuid { - if let data = characteristic.value { - let addr = Addr(data) - peers[id] = addr - delegate?.transport(self, didConnectToPeer: addr, withAddr: id) - } + if characteristic.uuid == CoreBluetoothTransport.identityCharacteristic.uuid { + peripheral.readValue(for: characteristic) } if characteristic.uuid == CoreBluetoothTransport.receiveCharacteristic.uuid { @@ -271,7 +265,17 @@ extension CoreBluetoothTransport: CBPeripheralDelegate { didUpdateValueFor characteristic: CBCharacteristic, error _: Error? ) { - guard let value = characteristic.value, let peer = peers[Addr(peripheral.identifier.bytes)] else { return } + + let id = Addr(peripheral.identifier.bytes) + if characteristic.uuid == CoreBluetoothTransport.identityCharacteristic.uuid { + guard let data = characteristic.value else { return } + let addr = Addr(data) + peers[id] = addr + delegate?.transport(self, didConnectToPeer: addr, withAddr: id) + return + } + + guard let value = characteristic.value, let peer = peers[id] else { return } delegate?.transport(self, didReceiveData: value, from: peer) } From 7a7493b0c18f3f020b0536c2a3a086f296bd116b Mon Sep 17 00:00:00 2001 From: decanus <7621705+decanus@users.noreply.github.com> Date: Tue, 22 Oct 2019 00:11:54 +0200 Subject: [PATCH 22/30] latest xcode --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 39002e0..b5f4a8b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ os: osx language: swift -osx_image: xcode10.3 +osx_image: xcode11.0 xcode_project: UB.xcodeproj xcode_scheme: UB.Package From 90df88eafdcb11f6c7589b1d26e76e210777582f Mon Sep 17 00:00:00 2001 From: decanus <7621705+decanus@users.noreply.github.com> Date: Tue, 22 Oct 2019 00:20:33 +0200 Subject: [PATCH 23/30] updated --- Sources/UB/Node.swift | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/Sources/UB/Node.swift b/Sources/UB/Node.swift index eace2f4..1dc3f2f 100644 --- a/Sources/UB/Node.swift +++ b/Sources/UB/Node.swift @@ -77,21 +77,22 @@ public class Node { } } - // @todo figure this out -// transports.forEach { _, transport in -// let peers = transport.peers -// // what this does is send a message to anyone that implements a specific service -// if message.service.count != 0 { -// let filtered = peers.filter { $0.services.contains { $0 == message.service } } -// if filtered.count > 0 { -// let sends = flood(message, data: data, transport: transport, peers: filtered) -// if sends > 0 { -// return -// } -// } -// } -// _ = flood(message, data: data, transport: transport, peers: peers) -// } + // @todo: there is probably some better way of doing this + transports.forEach { id, transport in + let transportPeers = peers.filter { $1.transports[id] != nil } + + if message.service.count != 0 { + let filtered = peers.filter { $0.services.contains { $0 == message.service } } + if filtered.count > 0 { + let sends = flood(message, data: data, transport: transport, peers: filtered) + if sends > 0 { + return + } + } + } + + _ = flood(message, data: data, transport: transport, peers: transportPeers) + } } private func flood(_ message: Message, data: Data, transport: Transport, peers: [Peer]) -> Int { From 25229e92389a6969bda140f03d10158b07585428 Mon Sep 17 00:00:00 2001 From: decanus <7621705+decanus@users.noreply.github.com> Date: Tue, 22 Oct 2019 00:21:36 +0200 Subject: [PATCH 24/30] fix --- Sources/UB/Node.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/UB/Node.swift b/Sources/UB/Node.swift index 1dc3f2f..402de07 100644 --- a/Sources/UB/Node.swift +++ b/Sources/UB/Node.swift @@ -82,7 +82,7 @@ public class Node { let transportPeers = peers.filter { $1.transports[id] != nil } if message.service.count != 0 { - let filtered = peers.filter { $0.services.contains { $0 == message.service } } + let filtered = transportPeers.filter { $1.services.contains { $0 == message.service } } if filtered.count > 0 { let sends = flood(message, data: data, transport: transport, peers: filtered) if sends > 0 { From 96474fbc20d83876cd854634eed41fa1c2c021db Mon Sep 17 00:00:00 2001 From: decanus <7621705+decanus@users.noreply.github.com> Date: Tue, 22 Oct 2019 00:24:04 +0200 Subject: [PATCH 25/30] fix --- Sources/UB/Node.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/UB/Node.swift b/Sources/UB/Node.swift index 402de07..b05539e 100644 --- a/Sources/UB/Node.swift +++ b/Sources/UB/Node.swift @@ -79,10 +79,10 @@ public class Node { // @todo: there is probably some better way of doing this transports.forEach { id, transport in - let transportPeers = peers.filter { $1.transports[id] != nil } + let transportPeers = Array(peers.filter({ $1.transports[id] != nil }).values) if message.service.count != 0 { - let filtered = transportPeers.filter { $1.services.contains { $0 == message.service } } + let filtered = transportPeers.filter { $0.services.contains { $0 == message.service } } if filtered.count > 0 { let sends = flood(message, data: data, transport: transport, peers: filtered) if sends > 0 { From 19ed83a8ec7b2475e03df389d9fffabe75b52ce0 Mon Sep 17 00:00:00 2001 From: decanus <7621705+decanus@users.noreply.github.com> Date: Tue, 22 Oct 2019 00:37:08 +0200 Subject: [PATCH 26/30] cleaned up send --- Sources/UB/Node.swift | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/Sources/UB/Node.swift b/Sources/UB/Node.swift index b05539e..bdb3319 100644 --- a/Sources/UB/Node.swift +++ b/Sources/UB/Node.swift @@ -79,38 +79,27 @@ public class Node { // @todo: there is probably some better way of doing this transports.forEach { id, transport in - let transportPeers = Array(peers.filter({ $1.transports[id] != nil }).values) + let transportPeers = Array( + peers.filter({ + $1.transports[id] != nil && $1.id != message.from && $1.id != message.origin + }).values + ) if message.service.count != 0 { let filtered = transportPeers.filter { $0.services.contains { $0 == message.service } } if filtered.count > 0 { - let sends = flood(message, data: data, transport: transport, peers: filtered) - if sends > 0 { - return + return filtered.forEach { + transport.send(message: data, to: $0.id) } } } - _ = flood(message, data: data, transport: transport, peers: transportPeers) + transportPeers.forEach { transport.send(message: data, to: $0.id) } } } - - private func flood(_ 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: data, to: $0.id) - } - - return sends - } +} // @todo create a message send loop with retransmissions and shit -} /// :nodoc: extension Node: TransportDelegate { From 1aedade4f457a13a05cb795e567750737044b75c Mon Sep 17 00:00:00 2001 From: decanus Date: Tue, 22 Oct 2019 18:48:54 +0200 Subject: [PATCH 27/30] updated --- Sources/UB/Node.swift | 12 ++-- .../Transports/CoreBluetoothTransport.swift | 65 ++++++++++++++----- Tests/UBTests/stubs/Transport.swift | 6 +- 3 files changed, 60 insertions(+), 23 deletions(-) diff --git a/Sources/UB/Node.swift b/Sources/UB/Node.swift index bdb3319..5e33215 100644 --- a/Sources/UB/Node.swift +++ b/Sources/UB/Node.swift @@ -1,6 +1,6 @@ +import CryptoKit import Foundation import SwiftProtobuf -import CryptoKit // @todo figure out architecture to support new forwarding algorithm. @@ -80,9 +80,9 @@ public class Node { // @todo: there is probably some better way of doing this transports.forEach { id, transport in let transportPeers = Array( - peers.filter({ + peers.filter { $1.transports[id] != nil && $1.id != message.from && $1.id != message.origin - }).values + }.values ) if message.service.count != 0 { @@ -94,12 +94,14 @@ public class Node { } } - transportPeers.forEach { transport.send(message: data, to: $0.id) } + transportPeers.forEach { + transport.send(message: data, to: $0.id) + } } } } - // @todo create a message send loop with retransmissions and shit +// @todo create a message send loop with retransmissions and shit /// :nodoc: extension Node: TransportDelegate { diff --git a/Sources/UB/Transports/CoreBluetoothTransport.swift b/Sources/UB/Transports/CoreBluetoothTransport.swift index 2332ff5..515c810 100644 --- a/Sources/UB/Transports/CoreBluetoothTransport.swift +++ b/Sources/UB/Transports/CoreBluetoothTransport.swift @@ -3,6 +3,8 @@ import Foundation /// CoreBluetoothTransport is used to send and receive message over Bluetooth public class CoreBluetoothTransport: NSObject { + /// :nodoc: + fileprivate var identity: UBID! /// :nodoc: public weak var delegate: TransportDelegate? @@ -90,7 +92,9 @@ public class CoreBluetoothTransport: NSObject { /// :nodoc: extension CoreBluetoothTransport: Transport { public func send(message: Data, to: Addr) { - if let peer = peripherals[to] { + guard let id = peers.first(where: { $0.value == to })?.key else { return } + + if let peer = peripherals[id] { return peer.peripheral.writeValue( message, for: peer.characteristic, @@ -98,7 +102,7 @@ extension CoreBluetoothTransport: Transport { ) } - if let central = centrals[to] { + if let central = centrals[id] { peripheralManager.updateValue( message, for: CoreBluetoothTransport.receiveCharacteristic, @@ -110,6 +114,8 @@ extension CoreBluetoothTransport: Transport { public func listen(identity: UBID) { state = .listening + self.identity = identity + if peripheralManager.state == .poweredOn { startAdvertising() } @@ -123,39 +129,65 @@ extension CoreBluetoothTransport: Transport { /// :nodoc: extension CoreBluetoothTransport: CBPeripheralManagerDelegate { public func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { - if peripheral.state == .poweredOn && state == .listening { + if peripheral.state == .poweredOn, state == .listening { startAdvertising() } } public func peripheralManager(_: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) { - for request in requests { - guard let data = request.value else { - // @todo - continue - } + requests + .filter { $0.characteristic.uuid == CoreBluetoothTransport.identityCharacteristic.uuid } + .forEach { request in - let id = Addr(request.central.identifier.bytes) + guard let data = request.value else { + return + } + + let id = Addr(request.central.identifier.bytes) + add(central: request.central) - if request.characteristic.uuid == CoreBluetoothTransport.identityCharacteristic.uuid { let addr = Addr(data) peers[id] = addr delegate?.transport(self, didConnectToPeer: addr, withAddr: id) + } + + for request in requests { + if request.characteristic.uuid == CoreBluetoothTransport.identityCharacteristic.uuid { + continue + } + + guard let data = request.value else { continue } + let id = Addr(request.central.identifier.bytes) guard let peer = peers[id] else { continue } delegate?.transport(self, didReceiveData: data, from: peer) + } + } + + public func peripheralManager(_: CBPeripheralManager, didReceiveRead request: CBATTRequest) { + if request.characteristic.uuid == CoreBluetoothTransport.identityCharacteristic.uuid { + peripheralManager.updateValue( + Data(identity), + for: CoreBluetoothTransport.identityCharacteristic, + onSubscribedCentrals: [request.central] + ) + add(central: request.central) } } public func peripheralManager( - _: CBPeripheralManager, + _ peripheral: CBPeripheralManager, central: CBCentral, - didSubscribeTo _: CBCharacteristic + didSubscribeTo characteristic: CBCharacteristic ) { add(central: central) + if characteristic.uuid == CoreBluetoothTransport.identityCharacteristic.uuid { + peripheral.updateValue(Data(identity), for: CoreBluetoothTransport.identityCharacteristic, onSubscribedCentrals: [central]) + return + } } public func peripheralManager( @@ -170,6 +202,7 @@ extension CoreBluetoothTransport: CBPeripheralManagerDelegate { } fileprivate func startAdvertising() { + if peripheralManager.isAdvertising { return } let service = CBMutableService(type: CoreBluetoothTransport.ubServiceUUID, primary: true) service.characteristics = [ @@ -188,7 +221,7 @@ extension CoreBluetoothTransport: CBPeripheralManagerDelegate { /// :nodoc: extension CoreBluetoothTransport: CBCentralManagerDelegate { public func centralManagerDidUpdateState(_ central: CBCentralManager) { - if central.state == .poweredOn && state == .listening { + if central.state == .poweredOn, state == .listening { startScanning() } } @@ -213,6 +246,7 @@ extension CoreBluetoothTransport: CBCentralManagerDelegate { } fileprivate func startScanning() { + if centralManager.isScanning { return } centralManager.scanForPeripherals(withServices: [CoreBluetoothTransport.ubServiceUUID]) } } @@ -241,7 +275,9 @@ extension CoreBluetoothTransport: CBPeripheralDelegate { guard let characteristics = service.characteristics else { return } for characteristic in characteristics { if characteristic.uuid == CoreBluetoothTransport.identityCharacteristic.uuid { + peripheral.setNotifyValue(true, for: characteristic) peripheral.readValue(for: characteristic) + peripheral.writeValue(Data(identity), for: characteristic, type: .withoutResponse) } if characteristic.uuid == CoreBluetoothTransport.receiveCharacteristic.uuid { @@ -249,7 +285,7 @@ extension CoreBluetoothTransport: CBPeripheralDelegate { } } - if peers[id] != nil, let characteristic = peripherals[id]?.characteristic { + if let characteristic = peripherals[id]?.characteristic { peripherals[id]?.peripheral.setNotifyValue(true, for: characteristic) } } @@ -265,7 +301,6 @@ extension CoreBluetoothTransport: CBPeripheralDelegate { didUpdateValueFor characteristic: CBCharacteristic, error _: Error? ) { - let id = Addr(peripheral.identifier.bytes) if characteristic.uuid == CoreBluetoothTransport.identityCharacteristic.uuid { guard let data = characteristic.value else { return } diff --git a/Tests/UBTests/stubs/Transport.swift b/Tests/UBTests/stubs/Transport.swift index 56af080..04f30b8 100644 --- a/Tests/UBTests/stubs/Transport.swift +++ b/Tests/UBTests/stubs/Transport.swift @@ -7,12 +7,12 @@ class Transport: UB.Transport { private(set) var sent: [(Data, Addr)] = [] var peers: [Peer] = [] - - init() { } + + init() {} func send(message: Data, to: Addr) { sent.append((message, to)) } - func listen(identity: UBID) {} + func listen(identity _: UBID) {} } From 23eb09741aed9b87c925f8d3d2594b4289e09850 Mon Sep 17 00:00:00 2001 From: decanus Date: Tue, 22 Oct 2019 18:50:20 +0200 Subject: [PATCH 28/30] updated --- Sources/UB/Transports/CoreBluetoothTransport.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Sources/UB/Transports/CoreBluetoothTransport.swift b/Sources/UB/Transports/CoreBluetoothTransport.swift index 515c810..0b21b3d 100644 --- a/Sources/UB/Transports/CoreBluetoothTransport.swift +++ b/Sources/UB/Transports/CoreBluetoothTransport.swift @@ -138,7 +138,6 @@ extension CoreBluetoothTransport: CBPeripheralManagerDelegate { requests .filter { $0.characteristic.uuid == CoreBluetoothTransport.identityCharacteristic.uuid } .forEach { request in - guard let data = request.value else { return } @@ -146,9 +145,8 @@ extension CoreBluetoothTransport: CBPeripheralManagerDelegate { let id = Addr(request.central.identifier.bytes) add(central: request.central) - let addr = Addr(data) peers[id] = addr - delegate?.transport(self, didConnectToPeer: addr, withAddr: id) + delegate?.transport(self, didConnectToPeer: Addr(data), withAddr: id) } for request in requests { @@ -160,8 +158,7 @@ extension CoreBluetoothTransport: CBPeripheralManagerDelegate { continue } - let id = Addr(request.central.identifier.bytes) - guard let peer = peers[id] else { continue } + guard let peer = peers[Addr(request.central.identifier.bytes)] else { continue } delegate?.transport(self, didReceiveData: data, from: peer) } } From ae164e4454bc78e3ba6c824fa9d2836c87d76b6f Mon Sep 17 00:00:00 2001 From: decanus Date: Tue, 22 Oct 2019 19:01:54 +0200 Subject: [PATCH 29/30] change --- Sources/UB/Transports/CoreBluetoothTransport.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Sources/UB/Transports/CoreBluetoothTransport.swift b/Sources/UB/Transports/CoreBluetoothTransport.swift index 0b21b3d..25443cf 100644 --- a/Sources/UB/Transports/CoreBluetoothTransport.swift +++ b/Sources/UB/Transports/CoreBluetoothTransport.swift @@ -279,12 +279,9 @@ extension CoreBluetoothTransport: CBPeripheralDelegate { if characteristic.uuid == CoreBluetoothTransport.receiveCharacteristic.uuid { peripherals[id] = (peripheral, characteristic) + peripheral.setNotifyValue(true, for: characteristic) } } - - if let characteristic = peripherals[id]?.characteristic { - peripherals[id]?.peripheral.setNotifyValue(true, for: characteristic) - } } public func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) { From e37e58f6a73d3793e3864d972b345764c8a27f44 Mon Sep 17 00:00:00 2001 From: decanus Date: Tue, 22 Oct 2019 19:09:24 +0200 Subject: [PATCH 30/30] updates --- Podfile | 2 +- Sources/UB/Transports/CoreBluetoothTransport.swift | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Podfile b/Podfile index 4e38668..56ea6f1 100644 --- a/Podfile +++ b/Podfile @@ -1,5 +1,5 @@ # Uncomment the next line to define a global platform for your project -# platform :ios, '9.0' +platform :ios, '13.0' target 'UB' do # Comment the next line if you don't want to use dynamic frameworks diff --git a/Sources/UB/Transports/CoreBluetoothTransport.swift b/Sources/UB/Transports/CoreBluetoothTransport.swift index 25443cf..815f9b4 100644 --- a/Sources/UB/Transports/CoreBluetoothTransport.swift +++ b/Sources/UB/Transports/CoreBluetoothTransport.swift @@ -145,8 +145,14 @@ extension CoreBluetoothTransport: CBPeripheralManagerDelegate { let id = Addr(request.central.identifier.bytes) add(central: request.central) + let addr = Addr(data) peers[id] = addr - delegate?.transport(self, didConnectToPeer: Addr(data), withAddr: id) + peripheralManager.updateValue( + Data(identity), + for: CoreBluetoothTransport.identityCharacteristic, + onSubscribedCentrals: [request.central] + ) + delegate?.transport(self, didConnectToPeer: addr, withAddr: id) } for request in requests {