Skip to content

Commit

Permalink
Initial implementation and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
insidegui committed Feb 28, 2020
1 parent 32222ce commit 6ed7e32
Show file tree
Hide file tree
Showing 10 changed files with 247 additions and 76 deletions.
7 changes: 7 additions & 0 deletions .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions Sources/MultipeerKit/Internal API/MockMultipeerConnection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Foundation

final class MockMultipeerConnection: MultipeerProtocol {

var didReceiveData: ((Data, PeerName) -> Void)?

var isRunning = false

func resume() {
isRunning = true
}

func stop() {
isRunning = false
}

func broadcast(_ data: Data) throws {
didReceiveData?(data, "MockPeer")
}

}
65 changes: 65 additions & 0 deletions Sources/MultipeerKit/Internal API/Models/MultipeerMessage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import Foundation

struct MultipeerMessage: Codable {
let type: String
let payload: Any?

init(type: String, payload: Any) {
self.type = type
self.payload = payload
}

enum CodingKeys: String, CodingKey {
case type
case payload
}

private typealias MessageDecoder = (KeyedDecodingContainer<CodingKeys>) throws -> Any
private typealias MessageEncoder = (Any, inout KeyedEncodingContainer<CodingKeys>) throws -> Void

private static var decoders: [String: MessageDecoder] = [:]
private static var encoders: [String: MessageEncoder] = [:]

static func register<T: Codable>(_ type: T.Type, for typeName: String, closure: @escaping (T) -> Void) {
decoders[typeName] = { container in
let payload = try container.decode(T.self, forKey: .payload)

DispatchQueue.main.async { closure(payload) }

return payload
}

encoders[typeName] = { payload, container in
try container.encode(payload as! T, forKey: .payload)
}
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
type = try container.decode(String.self, forKey: .type)

if let decode = Self.decoders[type] {
payload = try decode(container)
} else {
payload = nil
}
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)

try container.encode(type, forKey: .type)

if let payload = self.payload {
guard let encode = Self.encoders[type] else {
let context = EncodingError.Context(codingPath: [], debugDescription: "Invalid payload type: \(type).")
throw EncodingError.invalidValue(self, context)
}

try encode(payload, &container)
} else {
try container.encodeNil(forKey: .payload)
}
}

}
36 changes: 25 additions & 11 deletions Sources/MultipeerKit/Internal API/MultipeerConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,45 @@ import Foundation
import MultipeerConnectivity
import os.log

final class MultipeerConnection: NSObject {
final class MultipeerConnection: NSObject, MultipeerProtocol {

enum Mode: Int {
enum Mode: Int, CaseIterable {
case receiver
case transmitter
}

private let log = MultipeerKit.log(for: MultipeerConnection.self)

let mode: Mode
let modes: [Mode]
let configuration: MultipeerConfiguration
let me: MCPeerID

init(mode: Mode, configuration: MultipeerConfiguration = .default) {
self.mode = mode
init(modes: [Mode] = Mode.allCases, configuration: MultipeerConfiguration = .default) {
self.modes = modes
self.configuration = configuration
self.me = MCPeerID.fetchOrCreate(with: configuration)
}

var didReceiveData: ((Data, MCPeerID) -> Void)?
var didReceiveData: ((Data, PeerName) -> Void)?

func resume() {
os_log("%{public}@", log: log, type: .debug, #function)

if mode == .receiver {
if modes.contains(.receiver) {
advertiser.startAdvertisingPeer()
} else {
}
if modes.contains(.transmitter) {
browser.startBrowsingForPeers()
}
}

func stop() {
os_log("%{public}@", log: log, type: .debug, #function)

if mode == .receiver {
if modes.contains(.receiver) {
advertiser.stopAdvertisingPeer()
} else {
}
if modes.contains(.transmitter) {
browser.stopBrowsingForPeers()
}
}
Expand Down Expand Up @@ -68,6 +70,15 @@ final class MultipeerConnection: NSObject {
return a
}()

func broadcast(_ data: Data) throws {
guard !session.connectedPeers.isEmpty else {
os_log("Not broadcasting message: no connected peers", log: self.log, type: .error)
return
}

try session.send(data, toPeers: session.connectedPeers, with: .reliable)
}

}

// MARK: - Session delegate
Expand All @@ -81,7 +92,7 @@ extension MultipeerConnection: MCSessionDelegate {
func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
os_log("%{public}@", log: log, type: .debug, #function)

didReceiveData?(data, peerID)
didReceiveData?(data, peerID.displayName)
}

func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) {
Expand All @@ -104,6 +115,9 @@ extension MultipeerConnection: MCNearbyServiceBrowserDelegate {

func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) {
os_log("%{public}@", log: log, type: .debug, #function)

#warning("TODO: Add public API that can list/observe peers and customize invitation")
browser.invitePeer(peerID, to: session, withContext: nil, timeout: 10.0)
}

func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) {
Expand Down
14 changes: 14 additions & 0 deletions Sources/MultipeerKit/Internal API/MultipeerProtocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Foundation

typealias PeerName = String

protocol MultipeerProtocol: AnyObject {

var didReceiveData: ((Data, PeerName) -> Void)? { get set }

func resume()
func stop()

func broadcast(_ data: Data) throws

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public struct MultipeerConfiguration {
}

public static let `default` = MultipeerConfiguration(
serviceType: "MultipeerKit",
serviceType: "MKSVC",
peerName: MCPeerID.defaultDisplayName,
defaults: .standard
)
Expand Down
51 changes: 0 additions & 51 deletions Sources/MultipeerKit/Public API/MultipeerReceiver.swift

This file was deleted.

68 changes: 68 additions & 0 deletions Sources/MultipeerKit/Public API/MultipeerTransceiver.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import Foundation
import MultipeerConnectivity.MCPeerID
import os.log

public final class MultipeerTransceiver {

private let log = MultipeerKit.log(for: MultipeerTransceiver.self)

let connection: MultipeerProtocol

public init(configuration: MultipeerConfiguration = .default) {
self.connection = MultipeerConnection(
modes: MultipeerConnection.Mode.allCases,
configuration: configuration
)

configure(connection)
}

init(connection: MultipeerProtocol) {
self.connection = connection

configure(connection)
}

private func configure(_ connection: MultipeerProtocol) {
connection.didReceiveData = { [weak self] data, peer in
self?.handleDataReceived(data, from: peer)
}
}

public func receive<T: Codable>(_ type: T.Type, using closure: @escaping (T) -> Void) {
MultipeerMessage.register(type, for: String(describing: type), closure: closure)
}

public func resume() {
connection.resume()
}

public func stop() {
connection.stop()
}

public func broadcast<T: Encodable>(_ payload: T) {
do {
let message = MultipeerMessage(type: String(describing: T.self), payload: payload)

let data = try JSONEncoder().encode(message)

try connection.broadcast(data)
} catch {
os_log("Failed to send payload %@: %{public}@", log: self.log, type: .error, String(describing: payload), String(describing: error))
}
}

private func handleDataReceived(_ data: Data, from peer: PeerName) {
os_log("%{public}@", log: log, type: .debug, #function)

do {
let message = try JSONDecoder().decode(MultipeerMessage.self, from: data)

os_log("Received message %@", log: self.log, type: .debug, String(describing: message))
} catch {
os_log("Failed to decode message: %{public}@", log: self.log, type: .error, String(describing: error))
}
}

}
7 changes: 0 additions & 7 deletions Tests/LinuxMain.swift

This file was deleted.

Loading

0 comments on commit 6ed7e32

Please sign in to comment.