diff --git a/.changes/warp b/.changes/warp new file mode 100644 index 000000000..8b03fd619 --- /dev/null +++ b/.changes/warp @@ -0,0 +1 @@ +minor type="added" "Enable WARP (SPED: DTLS handshake carried in the ICE exchange, SNAP: SCTP association parameters in SDP), shortening connection and data channel setup. Always on and negotiated with the server: a server without WARP support gets a standard handshake" diff --git a/Sources/LiveKit/Core/RTC.swift b/Sources/LiveKit/Core/RTC.swift index 43e315c0a..8a493d12c 100644 --- a/Sources/LiveKit/Core/RTC.swift +++ b/Sources/LiveKit/Core/RTC.swift @@ -160,6 +160,10 @@ extension RTC { LKRTCInitializeSSL() + // WARP SPED (DTLS in STUN); SNAP is on `LKRTCConfiguration.liveKitDefault()`. + // Both engage only when the server negotiates them. + LKRTCPeerConnectionFactory.configureFieldTrials("\(kLKRTCFieldTrialIceHandshakeDtlsKey)/\(kLKRTCFieldTrialEnabledValue)/") + Room.log("Initializing PeerConnectionFactory...") return LKRTCPeerConnectionFactory(audioDeviceModuleType: admType.toRTCType(), diff --git a/Sources/LiveKit/Extensions/RTCConfiguration.swift b/Sources/LiveKit/Extensions/RTCConfiguration.swift index 999a5274d..9f671098b 100644 --- a/Sources/LiveKit/Extensions/RTCConfiguration.swift +++ b/Sources/LiveKit/Extensions/RTCConfiguration.swift @@ -26,6 +26,9 @@ extension LKRTCConfiguration { result.continualGatheringPolicy = .gatherContinually result.candidateNetworkPolicy = .all result.tcpCandidatePolicy = .enabled + // WARP SNAP (SCTP INIT in SDP). Immutable per peer connection, so it must never vary + // between creation and `setConfiguration`. + result.enableSctpSnap = true return result } diff --git a/Tests/LiveKitCoreTests/WARPTests.swift b/Tests/LiveKitCoreTests/WARPTests.swift new file mode 100644 index 000000000..4a726469b --- /dev/null +++ b/Tests/LiveKitCoreTests/WARPTests.swift @@ -0,0 +1,46 @@ +/* + * Copyright 2026 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@testable import LiveKit +import LiveKitWebRTC +import Testing + +/// WARP is offered unconditionally; the server decides whether it engages. +@Suite(.tags(.networking)) struct WARPTests { + private final class StubDelegate: TransportDelegate { + func transport(_: Transport, didUpdateState _: LKRTCPeerConnectionState) {} + func transport(_: Transport, didGenerateIceCandidate _: IceCandidate) {} + func transport(_: Transport, didOpenDataChannel _: LKRTCDataChannel) {} + func transport(_: Transport, didAddTrack _: RTCMediaTrack, rtpReceiver _: RTCReceiver, streamIds _: [String]) {} + func transport(_: Transport, didRemoveTrackWithId _: String) {} + func transportShouldNegotiate(_: Transport) {} + } + + @Test func offerAdvertisesSPEDAndSNAP() async throws { + let transport = try await Transport(config: .liveKitDefault(), + target: .publisher, + primary: true, + delegate: StubDelegate()) + defer { Task { await transport.close() } } + + _ = await transport.dataChannel(for: LKRTCDataChannel.Labels.reliable, + configuration: RTC.createDataChannelConfiguration()) + let sdp = try await transport.createOffer().sdp + + #expect(sdp.contains("goog-sped-v1"), "SPED: the WebRTC-IceHandshakeDtls field trial") + #expect(sdp.contains("a=sctp-init:"), "SNAP: enableSctpSnap") + } +}