Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ playground.xcworkspace
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
.build/
.swiftpm/

# CocoaPods
#
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

* **Unreleased** Add peer security configuration
- New: `PeerSecurityConfiguration`, `PeerCertificatePolicy`, and `PeerInvitationPolicy` for explicit session security and invitation handling
- New: `PeerDiscoveryInfo` support for advertised discovery metadata
- New: `foundPeerWithDiscoveryInfo` event case in `PeerConnectionEvent`; callers with exhaustive switches should handle this case or include `default`
- New: `PeerConnectivityUI` browser peer filtering with discovery metadata
- Compatibility: `foundPeer` remains emitted alongside `foundPeerWithDiscoveryInfo`, so listeners should handle one discovery event to avoid processing the same peer twice
- Compatibility: automatic non-manual invitation policies still emit `.receivedInvitation` for observation, but the event handler is a no-op and policy decisions remain authoritative

* **0.7.0** Fixed deprecated NSKeyedUnarchiver and added modern type-safe messaging API
- Fixed: Replaced deprecated `NSKeyedUnarchiver.unarchiveObject(with:)` with secure coding API
- New: `PeerMessage` protocol for type-safe, Codable-based messaging
Expand Down
8 changes: 8 additions & 0 deletions PeerConnectivity.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
3086CD2D1D09FB9900E269A3 /* PeerConnectivity.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3086CD221D09FB9800E269A3 /* PeerConnectivity.framework */; };
3086CD321D09FB9900E269A3 /* PeerConnectivityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3086CD311D09FB9900E269A3 /* PeerConnectivityTests.swift */; };
30PEERMSG2602020000000002 /* PeerMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30PEERMSG2602020000000001 /* PeerMessageTests.swift */; };
30SECURITY26060300000001 /* PeerSecurityConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30SECURITY26060300000002 /* PeerSecurityConfiguration.swift */; };
30SECURITY26060300000003 /* PeerSecurityConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30SECURITY26060300000004 /* PeerSecurityConfigurationTests.swift */; };
B20000022F30600000000001 /* ObservableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B20000022F30600000000002 /* ObservableTests.swift */; };
B20000022F30600000000003 /* PeerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B20000022F30600000000004 /* PeerTests.swift */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -65,6 +67,8 @@
3086CD2C1D09FB9900E269A3 /* PeerConnectivityTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PeerConnectivityTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3086CD311D09FB9900E269A3 /* PeerConnectivityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerConnectivityTests.swift; sourceTree = "<group>"; };
3086CD331D09FB9900E269A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
30SECURITY26060300000002 /* PeerSecurityConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PeerSecurityConfiguration.swift; path = Sources/PeerSecurityConfiguration.swift; sourceTree = "<group>"; };
30SECURITY26060300000004 /* PeerSecurityConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerSecurityConfigurationTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -104,6 +108,7 @@
3080C7E71D80A1D600AF9EA3 /* PeerConnectionManager.swift */,
3080C7E81D80A1D600AF9EA3 /* PeerConnectionResponder.swift */,
3080C7E91D80A1D600AF9EA3 /* PeerConnectivity.h */,
30SECURITY26060300000002 /* PeerSecurityConfiguration.swift */,
3080C7EA1D80A1D600AF9EA3 /* PeerSession.swift */,
3080C7EB1D80A1D600AF9EA3 /* PeerSessionEventProducer.swift */,
);
Expand Down Expand Up @@ -133,6 +138,7 @@
children = (
3086CD311D09FB9900E269A3 /* PeerConnectivityTests.swift */,
30PEERMSG2602020000000001 /* PeerMessageTests.swift */,
30SECURITY26060300000004 /* PeerSecurityConfigurationTests.swift */,
B20000022F30600000000002 /* ObservableTests.swift */,
B20000022F30600000000004 /* PeerTests.swift */,
3086CD331D09FB9900E269A3 /* Info.plist */,
Expand Down Expand Up @@ -264,6 +270,7 @@
3080C7F31D80A1D700AF9EA3 /* PeerAdvertiserEventProducer.swift in Sources */,
3080C7FC1D80A1D700AF9EA3 /* PeerSessionEventProducer.swift in Sources */,
3080C7F51D80A1D700AF9EA3 /* PeerBrowserAssisstant.swift in Sources */,
30SECURITY26060300000001 /* PeerSecurityConfiguration.swift in Sources */,
3080C7FB1D80A1D700AF9EA3 /* PeerSession.swift in Sources */,
3080C7EE1D80A1D700AF9EA3 /* Observable.swift in Sources */,
3080C7ED1D80A1D700AF9EA3 /* MultiObservable.swift in Sources */,
Expand All @@ -277,6 +284,7 @@
files = (
3086CD321D09FB9900E269A3 /* PeerConnectivityTests.swift in Sources */,
30PEERMSG2602020000000002 /* PeerMessageTests.swift in Sources */,
30SECURITY26060300000003 /* PeerSecurityConfigurationTests.swift in Sources */,
B20000022F30600000000001 /* ObservableTests.swift in Sources */,
B20000022F30600000000003 /* PeerTests.swift in Sources */,
);
Expand Down
12 changes: 9 additions & 3 deletions PeerConnectivityDemo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
309837421D8A8D600002338A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 309837411D8A8D600002338A /* Assets.xcassets */; };
309837451D8A8D600002338A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 309837431D8A8D600002338A /* LaunchScreen.storyboard */; };
B1E4E4E82F30592D00AE11AA /* PeerConnectivity in Frameworks */ = {isa = PBXBuildFile; productRef = B1E4E4E72F30592D00AE11AA /* PeerConnectivity */; };
B1E4E4EA2F30592D00AE11AA /* PeerConnectivityUI in Frameworks */ = {isa = PBXBuildFile; productRef = B1E4E4E92F30592D00AE11AA /* PeerConnectivityUI */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -42,6 +43,7 @@
buildActionMask = 2147483647;
files = (
B1E4E4E82F30592D00AE11AA /* PeerConnectivity in Frameworks */,
B1E4E4EA2F30592D00AE11AA /* PeerConnectivityUI in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -138,7 +140,7 @@
);
mainGroup = 3098372E1D8A8D600002338A;
packageReferences = (
B1E4E4E62F30592D00AE11AA /* XCLocalSwiftPackageReference "../PeerConnectivity" */,
B1E4E4E62F30592D00AE11AA /* XCLocalSwiftPackageReference "." */,
);
productRefGroup = 309837381D8A8D600002338A /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -375,9 +377,9 @@
/* End XCConfigurationList section */

/* Begin XCLocalSwiftPackageReference section */
B1E4E4E62F30592D00AE11AA /* XCLocalSwiftPackageReference "../PeerConnectivity" */ = {
B1E4E4E62F30592D00AE11AA /* XCLocalSwiftPackageReference "." */ = {
isa = XCLocalSwiftPackageReference;
relativePath = ../PeerConnectivity;
relativePath = .;
};
/* End XCLocalSwiftPackageReference section */

Expand All @@ -386,6 +388,10 @@
isa = XCSwiftPackageProductDependency;
productName = PeerConnectivity;
};
B1E4E4E92F30592D00AE11AA /* PeerConnectivityUI */ = {
isa = XCSwiftPackageProductDependency;
productName = PeerConnectivityUI;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 3098372F1D8A8D600002338A /* Project object */;
Expand Down
3 changes: 3 additions & 0 deletions PeerConnectivityDemo/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,9 @@ private extension ViewController {
case .foundPeer(let peer):
checkedItems.insert(.peerDiscovered)
appendLog(kind: "peer.found", detail: peer.displayName, peers: [peer])
case .foundPeerWithDiscoveryInfo(let peer, let discoveryInfo):
checkedItems.insert(.peerDiscovered)
appendLog(kind: "peer.found.metadata", detail: "\(peer.displayName): \(discoveryInfo ?? [:])", peers: [peer])
case .lostPeer(let peer):
appendLog(kind: "peer.lost", detail: peer.displayName, peers: [peer])
case .nearbyPeersChanged(let peers):
Expand Down
110 changes: 110 additions & 0 deletions PeerConnectivityTests/PeerConnectivityTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

import XCTest
import MultipeerConnectivity
@testable import PeerConnectivity

class PeerConnectivityTests: XCTestCase {
Expand Down Expand Up @@ -86,4 +87,113 @@ class PeerConnectivityTests: XCTestCase {
XCTFail("Expected status \(expected), got \(status)", file: file, line: line)
}
}

func testManagerStoresDiscoveryInfo() {
let discoveryInfo: PeerDiscoveryInfo = ["version": "1", "room": "lobby"]
let manager = PeerConnectionManager(serviceType: "disc-test", discoveryInfo: discoveryInfo)

XCTAssertEqual(manager.discoveryInfo?["version"], "1")
XCTAssertEqual(manager.discoveryInfo?["room"], "lobby")
manager.stop()
}

func testAdvertiserAndAssisstantStoreDiscoveryInfo() {
let discoveryInfo: PeerDiscoveryInfo = ["version": "1", "capability": "chat"]
let observer = Observable<PeerSessionEvent>(.none)
let producer = PeerSessionEventProducer(observer: observer)
let session = PeerSession(peer: Peer(displayName: "discovery-test"), eventProducer: producer)
let advertiserObserver = Observable<PeerAdvertiserEvent>(.none)
let advertiserProducer = PeerAdvertiserEventProducer(observer: advertiserObserver)
let assisstantObserver = Observable<PeerAdvertiserAssisstantEvent>(.none)
let assisstantProducer = PeerAdvertiserAssisstantEventProducer(observer: assisstantObserver)

let advertiser = PeerAdvertiser(
session: session,
serviceType: "disc-test",
discoveryInfo: discoveryInfo,
eventProducer: advertiserProducer
)
let assisstant = PeerAdvertiserAssisstant(
session: session,
serviceType: "disc-test",
discoveryInfo: discoveryInfo,
eventProducer: assisstantProducer
)

XCTAssertEqual(advertiser.discoveryInfo?["version"], "1")
XCTAssertEqual(assisstant.discoveryInfo?["capability"], "chat")
}

func testBrowserEventPreservesDiscoveryInfo() {
let observer = Observable<PeerBrowserEvent>(.none)
let producer = PeerBrowserEventProducer(observer: observer)
let browser = MCNearbyServiceBrowser(peer: MCPeerID(displayName: "local"), serviceType: "disc-test")
let remotePeerID = MCPeerID(displayName: "remote")
let discoveryInfo: PeerDiscoveryInfo = ["version": "1", "room": "lobby"]
var receivedDiscoveryInfo: PeerDiscoveryInfo?

observer.addObserver { event in
switch event {
case .foundPeer(_, let info):
receivedDiscoveryInfo = info
default: break
}
}

producer.browser(browser, foundPeer: remotePeerID, withDiscoveryInfo: discoveryInfo)

XCTAssertEqual(receivedDiscoveryInfo?["version"], "1")
XCTAssertEqual(receivedDiscoveryInfo?["room"], "lobby")
}

func testPublicFoundPeerWithDiscoveryInfoEventCarriesMetadata() {
let peer = Peer(displayName: "remote")
let discoveryInfo: PeerDiscoveryInfo = ["version": "1"]
let event = PeerConnectionEvent.foundPeerWithDiscoveryInfo(peer: peer, discoveryInfo: discoveryInfo)
var receivedDiscoveryInfo: PeerDiscoveryInfo?

switch event {
case .foundPeerWithDiscoveryInfo(_, let info):
receivedDiscoveryInfo = info
default:
XCTFail("Expected foundPeerWithDiscoveryInfo event")
}

XCTAssertEqual(receivedDiscoveryInfo?["version"], "1")
}

func testServiceTypeValidationAcceptsSupportedValues() {
XCTAssertTrue(PeerConnectionManager.isValidServiceType("chat"))
XCTAssertTrue(PeerConnectionManager.isValidServiceType("chat-1"))
XCTAssertTrue(PeerConnectionManager.isValidServiceType("abcdefghijklmn1"))
}

func testServiceTypeValidationRejectsUnsupportedValues() {
XCTAssertFalse(PeerConnectionManager.isValidServiceType(""))
XCTAssertFalse(PeerConnectionManager.isValidServiceType("abcdefghijklmnop"))
XCTAssertFalse(PeerConnectionManager.isValidServiceType("Chat"))
XCTAssertFalse(PeerConnectionManager.isValidServiceType("chat_room"))
XCTAssertFalse(PeerConnectionManager.isValidServiceType("chat.room"))
XCTAssertFalse(PeerConnectionManager.isValidServiceType("-chat"))
XCTAssertFalse(PeerConnectionManager.isValidServiceType("chat-"))
XCTAssertFalse(PeerConnectionManager.isValidServiceType("ab--c"))
XCTAssertFalse(PeerConnectionManager.isValidServiceType("123"))
}

func testDisplayNameValidationUsesUtf8ByteLength() {
XCTAssertTrue(Peer.isValidDisplayName("peer"))
XCTAssertTrue(Peer.isValidDisplayName(String(repeating: "a", count: 63)))
XCTAssertFalse(Peer.isValidDisplayName(""))
XCTAssertFalse(Peer.isValidDisplayName(String(repeating: "a", count: 64)))
XCTAssertFalse(Peer.isValidDisplayName(String(repeating: "é", count: 32)))
}

func testPeerCanWrapExistingPeerIdentifier() {
let peerID = MCPeerID(displayName: "remote")
let peer = Peer(peerID: peerID, status: .notConnected)

XCTAssertEqual(peer.displayName, "remote")
XCTAssertEqual(peer.status, .notConnected)
}

}
Loading