p2p-swift / Tests / IrdP2PTests / NegotiationTests.swift
479 words
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import Foundation
import Testing
@testable import IrdP2P
@testable import IrdP2PSignaling
import IrdP2PCrypto

@Suite struct NegotiationTests {
    @Test func lowerPeerOffersAndBothGoDirect() async throws {
        let h = Harness()
        let (a, ca) = try await h.join(peer: highPeer)
        let (b, cb) = try await h.join(peer: lowPeer)
        #expect(await ca.waitFor(.peerReady(lowPeer, .direct)))
        #expect(await cb.waitFor(.peerReady(highPeer, .direct)))
        // the offer came from the lower id: find the first signal frame carrying an offer, sent by lowPeer
        let offers = h.relay.frames.filter { $0.from == lowPeer }
        #expect(!offers.isEmpty)
        await a.leave(); await b.leave()
    }

    @Test func dataChannelLossFallsBackThenRestartsViaTheLadder() async throws {
        let h = Harness()
        let (a, ca) = try await h.join(peer: lowPeer)
        let (b, cb) = try await h.join(peer: highPeer)
        #expect(await ca.waitFor(.peerReady(highPeer, .direct)))
        #expect(await cb.waitFor(.peerReady(lowPeer, .direct)))
        #expect(await h.webrtc.connectionAttempts == 1)
        await h.webrtc.failAll()
        #expect(await ca.waitFor(timeout: .seconds(5)) { $0 == .peerReady(highPeer, .relayed) && ca.count(.peerReady(highPeer, .relayed)) >= 2 } != nil)
        #expect(await cb.waitFor(timeout: .seconds(5)) { $0 == .peerReady(lowPeer, .relayed) && cb.count(.peerReady(lowPeer, .relayed)) >= 2 } != nil)
        // relayed messaging works meanwhile
        try await a.send(to: highPeer, .json(.string("via relay")))
        #expect(await cb.waitFor(.message(from: lowPeer, .json(.string("via relay")))))
        // the offerer's ladder: its 2 s restart sleeper joins the two keepalive sleepers
        #expect(await eventuallyTrue { h.clock.sleeperCount >= 3 })
        h.clock.advance(by: .seconds(2))
        #expect(await eventuallyTrue { await h.webrtc.connectionAttempts == 2 })
        #expect(await ca.waitFor(timeout: .seconds(5)) { $0 == .peerReady(highPeer, .direct) && ca.count(.peerReady(highPeer, .direct)) >= 2 } != nil)
        #expect(await cb.waitFor(timeout: .seconds(5)) { $0 == .peerReady(lowPeer, .direct) && cb.count(.peerReady(lowPeer, .direct)) >= 2 } != nil)
        try await b.send(to: lowPeer, .json(.string("direct again")))
        #expect(await ca.waitFor(.message(from: highPeer, .json(.string("direct again")))))
        await a.leave(); await b.leave()
    }

    @Test func failedNegotiationsWalkTheLadderThenWaitForWake() async throws {
        let h = Harness()
        await h.webrtc.failNextConnections(10)
        let (a, ca) = try await h.join(peer: lowPeer)
        let (b, cb) = try await h.join(peer: highPeer)
        #expect(await ca.waitFor(.peerReady(highPeer, .relayed)))
        #expect(await cb.waitFor(.peerReady(lowPeer, .relayed)))
        #expect(await eventuallyTrue { await h.webrtc.connectionAttempts == 1 })   // the first negotiation failed
        var expected = 1
        for delay in Backoff.iceRestartLadder {
            // the offerer's restart sleeper is registered once the failure was processed
            #expect(await eventuallyTrue { h.clock.sleeperCount >= 3 })            // two keepalives + the restart
            h.clock.advance(by: delay)
            expected += 1
            let want = expected
            #expect(await eventuallyTrue { await h.webrtc.connectionAttempts == want }, "ladder step \(delay)")
        }
        #expect(!ca.contains(.peerReady(highPeer, .direct)))
        // out of ladder: nothing more is scheduled until wake()
        h.clock.advance(by: .seconds(60))
        try? await Task.sleep(for: .milliseconds(50))
        let finalAttempts = expected
        #expect(await h.webrtc.connectionAttempts == finalAttempts)
        await h.webrtc.failNextConnections(-10)
        await a.wake()
        let direct = await eventuallyTrue(timeout: .seconds(5)) {
            let ta = await a.transport(of: highPeer)
            let tb = await b.transport(of: lowPeer)
            return ta == .direct && tb == .direct
        }
        if !direct { Collector.dump("wake did not reconnect", h, [("a", ca), ("b", cb)]) }
        #expect(direct)
        await a.leave(); await b.leave()
    }

    @Test func relayOnlyRoomsNeverNegotiate() async throws {
        let h = Harness()
        let (a, ca) = try await h.join(peer: lowPeer, preferDataChannel: false)
        let (b, cb) = try await h.join(peer: highPeer, preferDataChannel: false)
        #expect(await ca.waitFor(.peerReady(highPeer, .relayed)))
        #expect(await cb.waitFor(.peerReady(lowPeer, .relayed)))
        try? await Task.sleep(for: .milliseconds(50))
        #expect(!ca.contains(.peerReady(highPeer, .direct)))
        #expect(await a.transport(of: highPeer) == .relayed)
        await a.leave(); await b.leave()
    }
}

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/p2p-swift.git